diff --git a/modules/controls/src/main/java/com/sun/javafx/scene/control/lists/BitSetReadOnlyUnbackedObservableList.java b/modules/controls/src/main/java/com/sun/javafx/scene/control/lists/BitSetReadOnlyUnbackedObservableList.java new file mode 100644 --- /dev/null +++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/lists/BitSetReadOnlyUnbackedObservableList.java @@ -0,0 +1,350 @@ +package com.sun.javafx.scene.control.lists; + +import com.sun.javafx.collections.NonIterableChange; +import com.sun.javafx.scene.control.MultipleAdditionAndRemovedChange; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableListBase; +import javafx.scene.control.ControlUtils; +import javafx.util.Callback; +import javafx.util.Pair; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static javafx.beans.binding.Bindings.select; + +public class BitSetReadOnlyUnbackedObservableList extends ObservableListBase + implements Cloneable { + private final BitSet bitset; + + private final Supplier modelSizeSupplier; + private final Consumer setSelectedIndex; + private final Supplier selectedIndexSupplier; + private final Supplier isAtomicSupplier; + + private int lastGetIndex = -1; + private int lastGetValue = -1; + + public BitSetReadOnlyUnbackedObservableList( + Supplier modelSizeSupplier, + Consumer setSelectedIndex, + Supplier selectedIndexSupplier, + Supplier isAtomicSupplier) { + this(new BitSet(), modelSizeSupplier, setSelectedIndex, selectedIndexSupplier, isAtomicSupplier); + } + + public BitSetReadOnlyUnbackedObservableList( + BitSet bitset, + Supplier modelSizeSupplier, + Consumer setSelectedIndex, + Supplier selectedIndexSupplier, + Supplier isAtomicSupplier) { + this.bitset = bitset; + this.modelSizeSupplier = modelSizeSupplier; + this.setSelectedIndex = setSelectedIndex; + this.selectedIndexSupplier = selectedIndexSupplier; + this.isAtomicSupplier = isAtomicSupplier; + } + + @Override public Integer get(int index) { + final int itemCount = modelSizeSupplier.get(); + if (index < 0 || index >= itemCount) { + return -1; + } + + if (index == (lastGetIndex + 1) && lastGetValue < itemCount) { + // we're iterating forward in order, short circuit for + // performance reasons (RT-39776) + lastGetIndex++; + lastGetValue = bitset.nextSetBit(lastGetValue + 1); + return lastGetValue; + } else if (index == (lastGetIndex - 1) && lastGetValue > 0) { + // we're iterating backward in order, short circuit for + // performance reasons (RT-39776) + lastGetIndex--; + lastGetValue = bitset.previousSetBit(lastGetValue - 1); + return lastGetValue; + } else { + for (lastGetIndex = 0, lastGetValue = bitset.nextSetBit(0); + lastGetValue >= 0 || lastGetIndex == index; + lastGetIndex++, lastGetValue = bitset.nextSetBit(lastGetValue + 1)) { + if (lastGetIndex == index) { + return lastGetValue; + } + } + } + + return -1; + } + + public void set(int index) { + maybeBeginChange(); + bitset.set(index); + nextAdd(index, index + 1); + maybeEndChange(); + } + + public void set(int startIndex, int endIndex, boolean state) { + maybeBeginChange(); + bitset.set(startIndex, endIndex, state); + nextAdd(startIndex, endIndex); + maybeEndChange(); + } + + public void clear(int index) { + maybeBeginChange(); + int removed = get(index); + bitset.clear(index); + nextRemove(index, removed); + maybeEndChange(); + } + + // Returns the number of true bits + @Override public int size() { + return bitset.cardinality(); + } + + private int getModelSize() { + return modelSizeSupplier.get(); + } + + @Override public boolean contains(Object o) { + if (o instanceof Number) { + Number n = (Number) o; + int index = n.intValue(); + + return index >= 0 && index < bitset.length() && + bitset.get(index); + } + + return false; + } + + public boolean isSelected(int index) { + // Note the change in semantics here - we used to check to ensure that + // the index is less than the item count, but now simply ensure that + // it is less than the length of the selectedIndices bitset. This helps + // to resolve issues such as RT-26721, where isSelected(int) was being + // called for indices that exceeded the item count, as a TreeItem (e.g. + // the root) was being collapsed. +// if (index >= 0 && index < getItemCount()) { + if (index >= 0 && index < bitset.length()) { + return bitset.get(index); + } + + return false; + } + + @Override protected Object clone() throws CloneNotSupportedException { + BitSetReadOnlyUnbackedObservableList clone = new BitSetReadOnlyUnbackedObservableList( + (BitSet) bitset.clone(), modelSizeSupplier, setSelectedIndex, selectedIndexSupplier, isAtomicSupplier); + return clone; + } + + public void clearAndSelect(int row) { + if (row < 0 || row >= getModelSize()) { + clearSelection(); + return; + } + + final boolean wasSelected = isSelected(row); + + // RT-33558 if this method has been called with a given row, and that + // row is the only selected row currently, then this method becomes a no-op. + if (wasSelected && size() == 1) { + // before we return, we double-check that the selected item + // is equal to the item in the given index + if (getSelectedItem() == getModelItem(row)) { + return; + } + } + + // firstly we make a copy of the selection, so that we can send out + // the correct details in the selection change event. + // We remove the new selection from the list seeing as it is not removed. + BitSet selectedIndicesCopy = new BitSet(); + selectedIndicesCopy.or(bitset); + selectedIndicesCopy.clear(row); + List previousSelectedIndices = new BitSetReadOnlyUnbackedObservableList(selectedIndicesCopy); + + // RT-32411 We used to call quietClearSelection() here, but this + // resulted in the selectedItems and selectedIndices lists never + // reporting that they were empty. + // makeAtomic toggle added to resolve RT-32618 + startAtomic(); + + // then clear the current selection + clearSelection(); + + // and select the new row + select(row); + stopAtomic(); + + // fire off a single add/remove/replace notification (rather than + // individual remove and add notifications) - see RT-33324 + ListChangeListener.Change change; + + /* + * getFrom() documentation: + * If wasAdded is true, the interval contains all the values that were added. + * If wasPermutated is true, the interval marks the values that were permutated. + * If wasRemoved is true and wasAdded is false, getFrom() and getTo() should + * return the same number - the place where the removed elements were positioned in the list. + */ + if (wasSelected) { + change = ControlUtils.buildClearAndSelectChange(selectedIndicesSeq, previousSelectedIndices, row); + } else { + int changeIndex = Math.max(0, selectedIndicesSeq.indexOf(row)); + change = new NonIterableChange.GenericAddRemoveChange<>( + changeIndex, changeIndex+1, previousSelectedIndices, selectedIndicesSeq); + } + + fireChange(change); + } + + public void reset() { + this.lastGetIndex = -1; + this.lastGetValue = -1; + } + + // package only + public void shiftSelection(int position, int shift, final Callback callback) { + shiftSelection(Arrays.asList(new Pair<>(position, shift)), callback); + } + + public void shiftSelection(List> shifts, final Callback callback) { + int selectedIndicesCardinality = bitset.size(); // number of true bits + if (selectedIndicesCardinality == 0) return; + + int selectedIndicesSize = bitset.size(); // number of bits reserved + + int[] perm = new int[selectedIndicesSize]; + Arrays.fill(perm, -1); + + // sort the list so that we iterate from highest position to lowest position + Collections.sort(shifts, (s1, s2) -> Integer.compare(s2.getKey(), s1.getKey())); + final int lowestShiftPosition = shifts.get(shifts.size() - 1).getKey(); + + // make a copy of the selectedIndices before so we can compare to it afterwards + BitSet selectedIndicesCopy = (BitSet) bitset.clone(); + + for (Pair shift : shifts) { + doShift(shift, callback, perm); + } + + // strip out all useless -1 default values from the perm array + final int[] prunedPerm = Arrays.stream(perm).filter(value -> value > -1).toArray(); + final boolean hasSelectionChanged = prunedPerm.length > 0; + + // This ensure that the selection remains accurate when a shift occurs. + final int selectedIndex = selectedIndexSupplier.get(); + if (selectedIndex >= lowestShiftPosition && selectedIndex > -1) { + // sum up the total shift, where the position is less than or equal + // to the previously selected index + int totalShift = shifts.stream() + .filter(shift -> shift.getKey() <= selectedIndex) + .mapToInt(shift -> shift.getValue()) + .sum(); + + // Fix for RT-38787: we used to not enter this block if + // selectedIndex + shift resulted in a value less than zero, whereas + // now we just set the newSelectionLead to zero in that instance. + // There exists unit tests that cover this. + final int newSelectionLead = Math.max(0, selectedIndex + totalShift); + + setSelectedIndex.accept(newSelectionLead); + + // added the selectedIndices call for RT-30356. + // changed to check if hasPermutated, and to call select(..) for RT-40010. + // This forces the selection event to go through the system and fire + // the necessary events. + if (hasSelectionChanged) { + bitset.set(newSelectionLead, true); + } else { + select(newSelectionLead); + } + + // removed due to RT-27185 +// focus(newSelectionLead); + } + + if (hasSelectionChanged) { + // work out what indices were removed and added + BitSet removed = (BitSet) selectedIndicesCopy.clone(); + removed.andNot(bitset); + + BitSet added = (BitSet) bitset.clone(); + added.andNot(selectedIndicesCopy); + + reset(); + selectedIndicesSeq.callObservers(new MultipleAdditionAndRemovedChange<>( + added.stream().boxed().collect(Collectors.toList()), + removed.stream().boxed().collect(Collectors.toList()), + selectedIndicesSeq + )); + } + } + + private void doShift(Pair shiftPair, final Callback callback, int[] perm) { + final int position = shiftPair.getKey(); + final int shift = shiftPair.getValue(); + + // with no check here, we get RT-15024 + if (position < 0) return; + if (shift == 0) return; + + int idx = (int) Arrays.stream(perm).filter(value -> value > -1).count(); + + int selectedIndicesSize = bitset.size() - idx; // number of bits reserved + + if (shift > 0) { + for (int i = selectedIndicesSize - 1; i >= position && i >= 0; i--) { + boolean selected = bitset.get(i); + + if (callback == null) { + bitset.clear(i); + bitset.set(i + shift, selected); + } else { + callback.call(new ShiftParams(i, i + shift, selected)); + } + + if (selected) { + perm[idx++] = i + 1; + } + } + bitset.clear(position); + } else if (shift < 0) { + for (int i = position; i < selectedIndicesSize; i++) { + if ((i + shift) < 0) continue; + if ((i + 1 + shift) < position) continue; + boolean selected = bitset.get(i + 1); + + if (callback == null) { + bitset.clear(i + 1); + bitset.set(i + 1 + shift, selected); + } else { + callback.call(new ShiftParams(i + 1, i + 1 + shift, selected)); + } + + if (selected) { + perm[idx++] = i; + } + } + } + } + + private void maybeBeginChange() { + if (isAtomicSupplier.get()) return; + beginChange(); + } + + private void maybeEndChange() { + if (isAtomicSupplier.get()) return; + endChange(); + } +} \ No newline at end of file diff --git a/modules/controls/src/main/java/com/sun/javafx/scene/control/SelectedItemsReadOnlyObservableList.java b/modules/controls/src/main/java/com/sun/javafx/scene/control/lists/SelectedItemsReadOnlyObservableList.java rename from modules/controls/src/main/java/com/sun/javafx/scene/control/SelectedItemsReadOnlyObservableList.java rename to modules/controls/src/main/java/com/sun/javafx/scene/control/lists/SelectedItemsReadOnlyObservableList.java --- a/modules/controls/src/main/java/com/sun/javafx/scene/control/SelectedItemsReadOnlyObservableList.java +++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/lists/SelectedItemsReadOnlyObservableList.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.sun.javafx.scene.control; +package com.sun.javafx.scene.control.lists; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -51,6 +51,7 @@ private final List> itemsRefList; + // FIXME this is a temporary thing! public boolean eventBlock = false; public SelectedItemsReadOnlyObservableList(ObservableList selectedIndices, Supplier modelSizeSupplier) { diff --git a/modules/controls/src/main/java/com/sun/javafx/scene/control/lists/ShiftParams.java b/modules/controls/src/main/java/com/sun/javafx/scene/control/lists/ShiftParams.java new file mode 100644 --- /dev/null +++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/lists/ShiftParams.java @@ -0,0 +1,25 @@ +package com.sun.javafx.scene.control.lists; + +public class ShiftParams { + private final int clearIndex; + private final int setIndex; + private final boolean selected; + + ShiftParams(int clearIndex, int setIndex, boolean selected) { + this.clearIndex = clearIndex; + this.setIndex = setIndex; + this.selected = selected; + } + + public final int getClearIndex() { + return clearIndex; + } + + public final int getSetIndex() { + return setIndex; + } + + public final boolean isSelected() { + return selected; + } +} \ No newline at end of file diff --git a/modules/controls/src/main/java/javafx/scene/control/ListView.java b/modules/controls/src/main/java/javafx/scene/control/ListView.java --- a/modules/controls/src/main/java/javafx/scene/control/ListView.java +++ b/modules/controls/src/main/java/javafx/scene/control/ListView.java @@ -31,7 +31,7 @@ import java.util.List; import com.sun.javafx.scene.control.Properties; -import com.sun.javafx.scene.control.SelectedItemsReadOnlyObservableList; +import com.sun.javafx.scene.control.lists.SelectedItemsReadOnlyObservableList; import com.sun.javafx.scene.control.behavior.ListCellBehavior; import javafx.beans.InvalidationListener; import javafx.beans.Observable; @@ -1378,7 +1378,7 @@ } if (!shifts.isEmpty()) { - shiftSelection(shifts, null); + selectedIndicesSeq.shiftSelection(shifts, null); } previousModelSize = getItemCount(); diff --git a/modules/controls/src/main/java/javafx/scene/control/MultipleSelectionModelBase.java b/modules/controls/src/main/java/javafx/scene/control/MultipleSelectionModelBase.java --- a/modules/controls/src/main/java/javafx/scene/control/MultipleSelectionModelBase.java +++ b/modules/controls/src/main/java/javafx/scene/control/MultipleSelectionModelBase.java @@ -32,8 +32,9 @@ import java.util.stream.Collectors; import com.sun.javafx.scene.control.MultipleAdditionAndRemovedChange; -import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; -import com.sun.javafx.scene.control.SelectedItemsReadOnlyObservableList; +import com.sun.javafx.scene.control.lists.BitSetReadOnlyUnbackedObservableList; +import com.sun.javafx.scene.control.lists.SelectedItemsReadOnlyObservableList; +import com.sun.javafx.scene.control.lists.ShiftParams; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.ListChangeListener.Change; @@ -67,10 +68,13 @@ setSelectedItem(getModelItem(getSelectedIndex())); }); - selectedIndices = new BitSet(); - selectedIndicesSeq = new BitSetReadOnlyUnbackedObservableList(selectedIndices); + selectedIndicesSeq = new BitSetReadOnlyUnbackedObservableList( + this::getItemCount, + this::setSelectedIndex, + this::getSelectedIndex, + this::isAtomic); - selectedItemsSeq = new SelectedItemsReadOnlyObservableList(selectedIndicesSeq, () -> getItemCount()) { + selectedItemsSeq = new SelectedItemsReadOnlyObservableList(selectedIndicesSeq, this::getItemCount) { @Override protected T getModelItem(int index) { return MultipleSelectionModelBase.this.getModelItem(index); } @@ -108,7 +112,7 @@ */ - final BitSet selectedIndices; +// final BitSet selectedIndices; final BitSetReadOnlyUnbackedObservableList selectedIndicesSeq; @Override public ObservableList getSelectedIndices() { return selectedIndicesSeq; @@ -169,215 +173,216 @@ protected abstract void focus(int index); protected abstract int getFocusedIndex(); - static class ShiftParams { - private final int clearIndex; - private final int setIndex; - private final boolean selected; +// static class ShiftParams { +// private final int clearIndex; +// private final int setIndex; +// private final boolean selected; +// +// ShiftParams(int clearIndex, int setIndex, boolean selected) { +// this.clearIndex = clearIndex; +// this.setIndex = setIndex; +// this.selected = selected; +// } +// +// public final int getClearIndex() { +// return clearIndex; +// } +// +// public final int getSetIndex() { +// return setIndex; +// } +// +// public final boolean isSelected() { +// return selected; +// } +// } - ShiftParams(int clearIndex, int setIndex, boolean selected) { - this.clearIndex = clearIndex; - this.setIndex = setIndex; - this.selected = selected; - } +// // package only +// void shiftSelection(int position, int shift, final Callback callback) { +// shiftSelection(Arrays.asList(new Pair<>(position, shift)), callback); +// } - public final int getClearIndex() { - return clearIndex; - } +// void shiftSelection(List> shifts, final Callback callback) { +// int selectedIndicesCardinality = selectedIndicesSeq.size(); +// if (selectedIndicesCardinality == 0) return; +// +// int selectedIndicesSize = selectedIndices.size(); // number of bits reserved +// +// int[] perm = new int[selectedIndicesSize]; +// Arrays.fill(perm, -1); +// +// // sort the list so that we iterate from highest position to lowest position +// Collections.sort(shifts, (s1, s2) -> Integer.compare(s2.getKey(), s1.getKey())); +// final int lowestShiftPosition = shifts.get(shifts.size() - 1).getKey(); +// +// // make a copy of the selectedIndices before so we can compare to it afterwards +// BitSet selectedIndicesCopy = (BitSet) selectedIndices.clone(); +// +// for (Pair shift : shifts) { +// doShift(shift, callback, perm); +// } +// +// // strip out all useless -1 default values from the perm array +// final int[] prunedPerm = Arrays.stream(perm).filter(value -> value > -1).toArray(); +// final boolean hasSelectionChanged = prunedPerm.length > 0; +// +// // This ensure that the selection remains accurate when a shift occurs. +// final int selectedIndex = getSelectedIndex(); +// if (selectedIndex >= lowestShiftPosition && selectedIndex > -1) { +// // sum up the total shift, where the position is less than or equal +// // to the previously selected index +// int totalShift = shifts.stream() +// .filter(shift -> shift.getKey() <= selectedIndex) +// .mapToInt(shift -> shift.getValue()) +// .sum(); +// +// // Fix for RT-38787: we used to not enter this block if +// // selectedIndex + shift resulted in a value less than zero, whereas +// // now we just set the newSelectionLead to zero in that instance. +// // There exists unit tests that cover this. +// final int newSelectionLead = Math.max(0, selectedIndex + totalShift); +// +// setSelectedIndex(newSelectionLead); +// +// // added the selectedIndices call for RT-30356. +// // changed to check if hasPermutated, and to call select(..) for RT-40010. +// // This forces the selection event to go through the system and fire +// // the necessary events. +// if (hasSelectionChanged) { +// selectedIndices.set(newSelectionLead, true); +// } else { +// select(newSelectionLead); +// } +// +// // removed due to RT-27185 +//// focus(newSelectionLead); +// } +// +// if (hasSelectionChanged) { +// // work out what indices were removed and added +// BitSet removed = (BitSet) selectedIndicesCopy.clone(); +// removed.andNot(selectedIndices); +// +// BitSet added = (BitSet) selectedIndices.clone(); +// added.andNot(selectedIndicesCopy); +// +// selectedIndicesSeq.reset(); +// selectedIndicesSeq.callObservers(new MultipleAdditionAndRemovedChange<>( +// added.stream().boxed().collect(Collectors.toList()), +// removed.stream().boxed().collect(Collectors.toList()), +// selectedIndicesSeq +// )); +// } +// } - public final int getSetIndex() { - return setIndex; - } - - public final boolean isSelected() { - return selected; - } - } - - // package only - void shiftSelection(int position, int shift, final Callback callback) { - shiftSelection(Arrays.asList(new Pair<>(position, shift)), callback); - } - - void shiftSelection(List> shifts, final Callback callback) { - int selectedIndicesCardinality = selectedIndices.cardinality(); // number of true bits - if (selectedIndicesCardinality == 0) return; - - int selectedIndicesSize = selectedIndices.size(); // number of bits reserved - - int[] perm = new int[selectedIndicesSize]; - Arrays.fill(perm, -1); - - // sort the list so that we iterate from highest position to lowest position - Collections.sort(shifts, (s1, s2) -> Integer.compare(s2.getKey(), s1.getKey())); - final int lowestShiftPosition = shifts.get(shifts.size() - 1).getKey(); - - // make a copy of the selectedIndices before so we can compare to it afterwards - BitSet selectedIndicesCopy = (BitSet) selectedIndices.clone(); - - for (Pair shift : shifts) { - doShift(shift, callback, perm); - } - - // strip out all useless -1 default values from the perm array - final int[] prunedPerm = Arrays.stream(perm).filter(value -> value > -1).toArray(); - final boolean hasSelectionChanged = prunedPerm.length > 0; - - // This ensure that the selection remains accurate when a shift occurs. - final int selectedIndex = getSelectedIndex(); - if (selectedIndex >= lowestShiftPosition && selectedIndex > -1) { - // sum up the total shift, where the position is less than or equal - // to the previously selected index - int totalShift = shifts.stream() - .filter(shift -> shift.getKey() <= selectedIndex) - .mapToInt(shift -> shift.getValue()) - .sum(); - - // Fix for RT-38787: we used to not enter this block if - // selectedIndex + shift resulted in a value less than zero, whereas - // now we just set the newSelectionLead to zero in that instance. - // There exists unit tests that cover this. - final int newSelectionLead = Math.max(0, selectedIndex + totalShift); - - setSelectedIndex(newSelectionLead); - - // added the selectedIndices call for RT-30356. - // changed to check if hasPermutated, and to call select(..) for RT-40010. - // This forces the selection event to go through the system and fire - // the necessary events. - if (hasSelectionChanged) { - selectedIndices.set(newSelectionLead, true); - } else { - select(newSelectionLead); - } - - // removed due to RT-27185 -// focus(newSelectionLead); - } - - if (hasSelectionChanged) { - // work out what indices were removed and added - BitSet removed = (BitSet) selectedIndicesCopy.clone(); - removed.andNot(selectedIndices); - - BitSet added = (BitSet) selectedIndices.clone(); - added.andNot(selectedIndicesCopy); - - selectedIndicesSeq.reset(); - selectedIndicesSeq.callObservers(new MultipleAdditionAndRemovedChange<>( - added.stream().boxed().collect(Collectors.toList()), - removed.stream().boxed().collect(Collectors.toList()), - selectedIndicesSeq - )); - } - } - - private void doShift(Pair shiftPair, final Callback callback, int[] perm) { - final int position = shiftPair.getKey(); - final int shift = shiftPair.getValue(); - - // with no check here, we get RT-15024 - if (position < 0) return; - if (shift == 0) return; - - int idx = (int) Arrays.stream(perm).filter(value -> value > -1).count(); - - int selectedIndicesSize = selectedIndices.size() - idx; // number of bits reserved - - if (shift > 0) { - for (int i = selectedIndicesSize - 1; i >= position && i >= 0; i--) { - boolean selected = selectedIndices.get(i); - - if (callback == null) { - selectedIndices.clear(i); - selectedIndices.set(i + shift, selected); - } else { - callback.call(new ShiftParams(i, i + shift, selected)); - } - - if (selected) { - perm[idx++] = i + 1; - } - } - selectedIndices.clear(position); - } else if (shift < 0) { - for (int i = position; i < selectedIndicesSize; i++) { - if ((i + shift) < 0) continue; - if ((i + 1 + shift) < position) continue; - boolean selected = selectedIndices.get(i + 1); - - if (callback == null) { - selectedIndices.clear(i + 1); - selectedIndices.set(i + 1 + shift, selected); - } else { - callback.call(new ShiftParams(i + 1, i + 1 + shift, selected)); - } - - if (selected) { - perm[idx++] = i; - } - } - } - } +// private void doShift(Pair shiftPair, final Callback callback, int[] perm) { +// final int position = shiftPair.getKey(); +// final int shift = shiftPair.getValue(); +// +// // with no check here, we get RT-15024 +// if (position < 0) return; +// if (shift == 0) return; +// +// int idx = (int) Arrays.stream(perm).filter(value -> value > -1).count(); +// +// int selectedIndicesSize = selectedIndices.size() - idx; // number of bits reserved +// +// if (shift > 0) { +// for (int i = selectedIndicesSize - 1; i >= position && i >= 0; i--) { +// boolean selected = selectedIndices.get(i); +// +// if (callback == null) { +// selectedIndices.clear(i); +// selectedIndices.set(i + shift, selected); +// } else { +// callback.call(new ShiftParams(i, i + shift, selected)); +// } +// +// if (selected) { +// perm[idx++] = i + 1; +// } +// } +// selectedIndices.clear(position); +// } else if (shift < 0) { +// for (int i = position; i < selectedIndicesSize; i++) { +// if ((i + shift) < 0) continue; +// if ((i + 1 + shift) < position) continue; +// boolean selected = selectedIndices.get(i + 1); +// +// if (callback == null) { +// selectedIndices.clear(i + 1); +// selectedIndices.set(i + 1 + shift, selected); +// } else { +// callback.call(new ShiftParams(i + 1, i + 1 + shift, selected)); +// } +// +// if (selected) { +// perm[idx++] = i; +// } +// } +// } +// } @Override public void clearAndSelect(int row) { - if (row < 0 || row >= getItemCount()) { - clearSelection(); - return; - } - - final boolean wasSelected = isSelected(row); - - // RT-33558 if this method has been called with a given row, and that - // row is the only selected row currently, then this method becomes a no-op. - if (wasSelected && getSelectedIndices().size() == 1) { - // before we return, we double-check that the selected item - // is equal to the item in the given index - if (getSelectedItem() == getModelItem(row)) { - return; - } - } - - // firstly we make a copy of the selection, so that we can send out - // the correct details in the selection change event. - // We remove the new selection from the list seeing as it is not removed. - BitSet selectedIndicesCopy = new BitSet(); - selectedIndicesCopy.or(selectedIndices); - selectedIndicesCopy.clear(row); - List previousSelectedIndices = new BitSetReadOnlyUnbackedObservableList(selectedIndicesCopy); - - // RT-32411 We used to call quietClearSelection() here, but this - // resulted in the selectedItems and selectedIndices lists never - // reporting that they were empty. - // makeAtomic toggle added to resolve RT-32618 - startAtomic(); - - // then clear the current selection - clearSelection(); - - // and select the new row - select(row); - stopAtomic(); - - // fire off a single add/remove/replace notification (rather than - // individual remove and add notifications) - see RT-33324 - ListChangeListener.Change change; - - /* - * getFrom() documentation: - * If wasAdded is true, the interval contains all the values that were added. - * If wasPermutated is true, the interval marks the values that were permutated. - * If wasRemoved is true and wasAdded is false, getFrom() and getTo() should - * return the same number - the place where the removed elements were positioned in the list. - */ - if (wasSelected) { - change = ControlUtils.buildClearAndSelectChange(selectedIndicesSeq, previousSelectedIndices, row); - } else { - int changeIndex = Math.max(0, selectedIndicesSeq.indexOf(row)); - change = new NonIterableChange.GenericAddRemoveChange<>( - changeIndex, changeIndex+1, previousSelectedIndices, selectedIndicesSeq); - } - - selectedIndicesSeq.callObservers(change); +// if (row < 0 || row >= getItemCount()) { +// clearSelection(); +// return; +// } +// +// final boolean wasSelected = isSelected(row); +// +// // RT-33558 if this method has been called with a given row, and that +// // row is the only selected row currently, then this method becomes a no-op. +// if (wasSelected && getSelectedIndices().size() == 1) { +// // before we return, we double-check that the selected item +// // is equal to the item in the given index +// if (getSelectedItem() == getModelItem(row)) { +// return; +// } +// } +// +// // firstly we make a copy of the selection, so that we can send out +// // the correct details in the selection change event. +// // We remove the new selection from the list seeing as it is not removed. +// BitSet selectedIndicesCopy = new BitSet(); +// selectedIndicesCopy.or(selectedIndices); +// selectedIndicesCopy.clear(row); +// List previousSelectedIndices = new BitSetReadOnlyUnbackedObservableList(selectedIndicesCopy); +// +// // RT-32411 We used to call quietClearSelection() here, but this +// // resulted in the selectedItems and selectedIndices lists never +// // reporting that they were empty. +// // makeAtomic toggle added to resolve RT-32618 +// startAtomic(); +// +// // then clear the current selection +// clearSelection(); +// +// // and select the new row +// select(row); +// stopAtomic(); +// +// // fire off a single add/remove/replace notification (rather than +// // individual remove and add notifications) - see RT-33324 +// ListChangeListener.Change change; +// +// /* +// * getFrom() documentation: +// * If wasAdded is true, the interval contains all the values that were added. +// * If wasPermutated is true, the interval marks the values that were permutated. +// * If wasRemoved is true and wasAdded is false, getFrom() and getTo() should +// * return the same number - the place where the removed elements were positioned in the list. +// */ +// if (wasSelected) { +// change = ControlUtils.buildClearAndSelectChange(selectedIndicesSeq, previousSelectedIndices, row); +// } else { +// int changeIndex = Math.max(0, selectedIndicesSeq.indexOf(row)); +// change = new NonIterableChange.GenericAddRemoveChange<>( +// changeIndex, changeIndex+1, previousSelectedIndices, selectedIndicesSeq); +// } +// +// selectedIndicesSeq.callObservers(change); + selectedIndicesSeq.clearAndSelect(row); } @Override public void select(int row) { @@ -396,11 +401,11 @@ boolean fireUpdatedItemEvent = isSameRow && ! isSameItem; startAtomic(); - if (! selectedIndices.get(row)) { + if (!selectedIndicesSeq.isSelected(row)) { if (getSelectionMode() == SINGLE) { quietClearSelection(); } - selectedIndices.set(row); + selectedIndicesSeq.set(row); } setSelectedIndex(row); @@ -476,15 +481,15 @@ for (int i = rows.length - 1; i >= 0; i--) { int index = rows[i]; if (index >= 0 && index < rowCount) { - selectedIndices.set(index); + selectedIndicesSeq.set(index); select(index); break; } } - if (selectedIndices.isEmpty()) { + if (selectedIndicesSeq.isEmpty()) { if (row > 0 && row < rowCount) { - selectedIndices.set(row); + selectedIndicesSeq.set(row); select(row); } } @@ -496,8 +501,8 @@ int lastIndex = -1; if (row >= 0 && row < rowCount) { lastIndex = row; - if (! selectedIndices.get(row)) { - selectedIndices.set(row); + if (! selectedIndicesSeq.isSelected(row)) { + selectedIndicesSeq.set(row); actualSelectedRows.add(row); } } @@ -507,8 +512,8 @@ if (index < 0 || index >= rowCount) continue; lastIndex = index; - if (! selectedIndices.get(index)) { - selectedIndices.set(index); + if (! selectedIndicesSeq.isSelected(index)) { + selectedIndicesSeq.set(index); actualSelectedRows.add(index); } } @@ -621,7 +626,7 @@ // set all selected indices to true clearSelection(); - selectedIndices.set(0, rowCount, true); + selectedIndicesSeq.set(0, rowCount, true); selectedIndicesSeq.callObservers(new NonIterableChange.SimpleAddChange<>(0, rowCount, selectedIndicesSeq)); if (focusedIndex == -1) { @@ -659,10 +664,10 @@ // TODO shouldn't directly access like this // TODO might need to update focus and / or selected index/item - boolean wasEmpty = selectedIndices.isEmpty(); - selectedIndices.clear(index); + boolean wasEmpty = selectedIndicesSeq.isEmpty(); + selectedIndicesSeq.clear(index); - if (! wasEmpty && selectedIndices.isEmpty()) { + if (! wasEmpty && selectedIndicesSeq.isEmpty()) { clearSelection(); } @@ -676,40 +681,30 @@ } @Override public void clearSelection() { - List removed = new BitSetReadOnlyUnbackedObservableList((BitSet) selectedIndices.clone()); +// List removed = new BitSetReadOnlyUnbackedObservableList((BitSet) selectedIndices.clone()); quietClearSelection(); if (! isAtomic()) { setSelectedIndex(-1); focus(-1); - selectedIndicesSeq.callObservers( - new NonIterableChange.GenericAddRemoveChange<>(0, 0, - removed, selectedIndicesSeq)); +// selectedIndicesSeq.callObservers( +// new NonIterableChange.GenericAddRemoveChange<>(0, 0, +// removed, selectedIndicesSeq)); + selectedIndicesSeq.clear(); } } private void quietClearSelection() { - selectedIndices.clear(); + selectedIndicesSeq.clear(); } @Override public boolean isSelected(int index) { - // Note the change in semantics here - we used to check to ensure that - // the index is less than the item count, but now simply ensure that - // it is less than the length of the selectedIndices bitset. This helps - // to resolve issues such as RT-26721, where isSelected(int) was being - // called for indices that exceeded the item count, as a TreeItem (e.g. - // the root) was being collapsed. -// if (index >= 0 && index < getItemCount()) { - if (index >= 0 && index < selectedIndices.length()) { - return selectedIndices.get(index); - } - - return false; + return selectedIndicesSeq.isSelected(index); } @Override public boolean isEmpty() { - return selectedIndices.isEmpty(); + return selectedIndicesSeq.isEmpty(); } @Override public void selectPrevious() { @@ -748,70 +743,5 @@ * * **********************************************************************/ - class BitSetReadOnlyUnbackedObservableList extends ReadOnlyUnbackedObservableList { - private final BitSet bitset; - private int lastGetIndex = -1; - private int lastGetValue = -1; - - public BitSetReadOnlyUnbackedObservableList() { - this(new BitSet()); - } - - public BitSetReadOnlyUnbackedObservableList(BitSet bitset) { - this.bitset = bitset; - } - - @Override public Integer get(int index) { - final int itemCount = getItemCount(); - if (index < 0 || index >= itemCount) { - return -1; - } - - if (index == (lastGetIndex + 1) && lastGetValue < itemCount) { - // we're iterating forward in order, short circuit for - // performance reasons (RT-39776) - lastGetIndex++; - lastGetValue = bitset.nextSetBit(lastGetValue + 1); - return lastGetValue; - } else if (index == (lastGetIndex - 1) && lastGetValue > 0) { - // we're iterating backward in order, short circuit for - // performance reasons (RT-39776) - lastGetIndex--; - lastGetValue = bitset.previousSetBit(lastGetValue - 1); - return lastGetValue; - } else { - for (lastGetIndex = 0, lastGetValue = bitset.nextSetBit(0); - lastGetValue >= 0 || lastGetIndex == index; - lastGetIndex++, lastGetValue = bitset.nextSetBit(lastGetValue + 1)) { - if (lastGetIndex == index) { - return lastGetValue; - } - } - } - - return -1; - } - - @Override public int size() { - return bitset.cardinality(); - } - - @Override public boolean contains(Object o) { - if (o instanceof Number) { - Number n = (Number) o; - int index = n.intValue(); - - return index >= 0 && index < bitset.length() && - bitset.get(index); - } - - return false; - } - - public void reset() { - this.lastGetIndex = -1; - this.lastGetValue = -1; - } - } } diff --git a/modules/controls/src/main/java/javafx/scene/control/TableView.java b/modules/controls/src/main/java/javafx/scene/control/TableView.java --- a/modules/controls/src/main/java/javafx/scene/control/TableView.java +++ b/modules/controls/src/main/java/javafx/scene/control/TableView.java @@ -39,7 +39,7 @@ import com.sun.javafx.scene.control.Logging; import com.sun.javafx.scene.control.Properties; import com.sun.javafx.scene.control.SelectedCellsMap; -import com.sun.javafx.scene.control.SelectedItemsReadOnlyObservableList; +import com.sun.javafx.scene.control.lists.SelectedItemsReadOnlyObservableList; import com.sun.javafx.scene.control.behavior.TableCellBehavior; import com.sun.javafx.scene.control.behavior.TableCellBehaviorBase; import javafx.beans.*; @@ -57,7 +57,6 @@ import javafx.collections.ListChangeListener; import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; -import javafx.collections.ObservableListBase; import javafx.collections.WeakListChangeListener; import javafx.collections.transformation.SortedList; import javafx.css.CssMetaData; @@ -2999,8 +2998,8 @@ final TablePosition tp = removed.get(i); final int row = tp.getRow(); - if (selectedIndices.get(row)) { - selectedIndices.clear(row); + if (selectedIndicesSeq.isSelected(row)) { + selectedIndicesSeq.clear(row); newlyUnselectedRows.add(row); } } @@ -3011,8 +3010,8 @@ final TablePosition tp = added.get(i); final int row = tp.getRow(); - if (! selectedIndices.get(row)) { - selectedIndices.set(row); + if (! selectedIndicesSeq.isSelected(row)) { + selectedIndicesSeq.set(row); newlySelectedRows.add(row); } } diff --git a/modules/controls/src/main/java/javafx/scene/control/TreeTableView.java b/modules/controls/src/main/java/javafx/scene/control/TreeTableView.java --- a/modules/controls/src/main/java/javafx/scene/control/TreeTableView.java +++ b/modules/controls/src/main/java/javafx/scene/control/TreeTableView.java @@ -31,11 +31,11 @@ import com.sun.javafx.scene.control.Properties; import com.sun.javafx.scene.control.SelectedCellsMap; -import com.sun.javafx.scene.control.SelectedItemsReadOnlyObservableList; +import com.sun.javafx.scene.control.lists.SelectedItemsReadOnlyObservableList; import com.sun.javafx.scene.control.behavior.TableCellBehaviorBase; import com.sun.javafx.scene.control.behavior.TreeTableCellBehavior; +import com.sun.javafx.scene.control.lists.ShiftParams; import javafx.beans.property.DoubleProperty; -import javafx.collections.ObservableListBase; import javafx.css.CssMetaData; import javafx.css.PseudoClass; @@ -2315,7 +2315,7 @@ this.treeTableView.rootProperty().addListener(weakRootPropertyListener); this.treeTableView.showRootProperty().addListener(o -> { - shiftSelection(0, treeTableView.isShowRoot() ? 1 : -1, null); + selectedIndicesSeq.shiftSelection(0, treeTableView.isShowRoot() ? 1 : -1, null); }); updateTreeEventListener(null, treeTableView.getRoot()); @@ -2528,7 +2528,7 @@ } } while (e.getChange() != null && e.getChange().next()); - shiftSelection(startRow, shift, new Callback() { + selectedIndicesSeq.shiftSelection(startRow, shift, new Callback() { @Override public Void call(ShiftParams param) { // we make the shifts atomic, as otherwise listeners to @@ -3244,8 +3244,8 @@ final TreeTablePosition tp = removed.get(i); final int row = tp.getRow(); - if (selectedIndices.get(row)) { - selectedIndices.clear(row); + if (selectedIndicesSeq.isSelected(row)) { + selectedIndicesSeq.clear(row); newlyUnselectedRows.add(row); } } @@ -3256,8 +3256,8 @@ final TreeTablePosition tp = added.get(i); final int row = tp.getRow(); - if (! selectedIndices.get(row)) { - selectedIndices.set(row); + if (!selectedIndicesSeq.isSelected(row)) { + selectedIndicesSeq.set(row); newlySelectedRows.add(row); } } diff --git a/modules/controls/src/main/java/javafx/scene/control/TreeView.java b/modules/controls/src/main/java/javafx/scene/control/TreeView.java --- a/modules/controls/src/main/java/javafx/scene/control/TreeView.java +++ b/modules/controls/src/main/java/javafx/scene/control/TreeView.java @@ -1252,7 +1252,7 @@ this.treeView = treeView; this.treeView.rootProperty().addListener(weakRootPropertyListener); this.treeView.showRootProperty().addListener(o -> { - shiftSelection(0, treeView.isShowRoot() ? 1 : -1, null); + selectedIndicesSeq.shiftSelection(0, treeView.isShowRoot() ? 1 : -1, null); }); updateTreeEventListener(null, treeView.getRoot()); @@ -1337,7 +1337,7 @@ // in this change. ListChangeListener.Change newChange = new NonIterableChange.GenericAddRemoveChange<>(from, from, removed, selectedIndicesSeq); - selectedIndicesSeq.callObservers(newChange); +// selectedIndicesSeq.callObservers(newChange); } shift += -count + 1; @@ -1396,7 +1396,7 @@ } } while (e.getChange() != null && e.getChange().next()); - shiftSelection(startRow, shift, null); + selectedIndicesSeq.shiftSelection(startRow, shift, null); if (e.wasAdded() || e.wasRemoved()) { Integer anchor = TreeCellBehavior.getAnchor(treeView, null);