Edit C:\Program Files\Java\jdk1.8.0_121\javafx\scene\control\MultipleSelectionModelBase.java
/* * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package javafx.scene.control; import com.sun.javafx.collections.MappingChange; import com.sun.javafx.collections.NonIterableChange; import static javafx.scene.control.SelectionMode.SINGLE; import java.util.AbstractList; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.List; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.ListChangeListener.Change; import javafx.util.Callback; import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; /** * An abstract class that implements more of the abstract MultipleSelectionModel * abstract class. However, this class is package-protected and not intended * for public use. * * @param <T> The type of the underlying data model for the UI control. */ abstract class MultipleSelectionModelBase<T> extends MultipleSelectionModel<T> { /*********************************************************************** * * * Constructors * * * **********************************************************************/ public MultipleSelectionModelBase() { selectedIndexProperty().addListener(valueModel -> { // we used to lazily retrieve the selected item, but now we just // do it when the selection changes. This is hardly likely to be // expensive, and we still lazily handle the multiple selection // cases over in MultipleSelectionModel. setSelectedItem(getModelItem(getSelectedIndex())); }); selectedIndices = new BitSet(); selectedIndicesSeq = new BitSetReadOnlyUnbackedObservableList(selectedIndices); final MappingChange.Map<Integer,T> map = f -> getModelItem(f); selectedIndicesSeq.addListener(new ListChangeListener<Integer>() { @Override public void onChanged(final Change<? extends Integer> c) { // when the selectedIndices ObservableList changes, we manually call // the observers of the selectedItems ObservableList. // Fix for a bug identified whilst fixing RT-37395: // We shouldn't fire events on the selectedItems list unless // the indices list has actually changed. This means that index // permutation events should not be forwarded blindly through the // items list, as a index permutation implies the items list is // unchanged, not changed! boolean hasRealChangeOccurred = false; while (c.next() && ! hasRealChangeOccurred) { hasRealChangeOccurred = c.wasAdded() || c.wasRemoved(); } if (hasRealChangeOccurred) { if (selectedItemChange != null) { selectedItemsSeq.callObservers(selectedItemChange); } else { c.reset(); selectedItemsSeq.callObservers(new MappingChange<Integer, T>(c, map, selectedItemsSeq)); } } c.reset(); } }); selectedItemsSeq = new ReadOnlyUnbackedObservableList<T>() { @Override public T get(int i) { int pos = selectedIndicesSeq.get(i); return getModelItem(pos); } @Override public int size() { return selectedIndices.cardinality(); } }; } /*********************************************************************** * * * Observable properties * * * **********************************************************************/ /* * We only maintain the values of the selectedIndex and selectedIndices * properties. The value of the selectedItem and selectedItems properties * is determined on-demand. We fire the SELECTED_ITEM and SELECTED_ITEMS * property change events whenever the related SELECTED_INDEX or * SELECTED_INDICES properties change. * * This means that the cost of the ListViewSelectionModel is cheap in most * cases, assuming that the end-consumer isn't calling getSelectedItems * too aggressively. Of course, this is only an issue when the ListViewModel * is being populated by some remote, expensive to query data source. * * In addition, we do not provide ObservableLists for the selected indices or the * selected items properties, as this would allow the API consumer to add * observers to these ObservableLists. This would make life tougher as we would * then be forced to keep these ObservableLists in-sync at all times, which for * the selectedItems ObservableList, would require potentially a lot of work and * memory. Instead, we return a List, and allow for changes to these Lists * to be observed through the SELECTED_INDICES and SELECTED_ITEMS * properties. */ final BitSet selectedIndices; final BitSetReadOnlyUnbackedObservableList selectedIndicesSeq; @Override public ObservableList<Integer> getSelectedIndices() { return selectedIndicesSeq; } private final ReadOnlyUnbackedObservableList<T> selectedItemsSeq; @Override public ObservableList<T> getSelectedItems() { return selectedItemsSeq; } /*********************************************************************** * * * Internal field * * * **********************************************************************/ ListChangeListener.Change selectedItemChange; // Fix for RT-20945 (and numerous other issues!) private int atomicityCount = 0; boolean isAtomic() { return atomicityCount > 0; } void startAtomic() { atomicityCount++; } void stopAtomic() { atomicityCount = Math.max(0, --atomicityCount); } /*********************************************************************** * * * Public selection API * * * **********************************************************************/ /** * Returns the number of items in the data model that underpins the control. * An example would be that a ListView selection model would likely return * <code>listView.getItems().size()</code>. The valid range of selectable * indices is between 0 and whatever is returned by this method. */ protected abstract int getItemCount(); /** * Returns the item at the given index. An example using ListView would be * <code>listView.getItems().get(index)</code>. * * @param index The index of the item that is requested from the underlying * data model. * @return Returns null if the index is out of bounds, or an element of type * T that is related to the given index. */ protected abstract T getModelItem(int index); 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; 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; } } // package only void shiftSelection(int position, int shift, final Callback<ShiftParams, Void> callback) { // with no check here, we get RT-15024 if (position < 0) return; if (shift == 0) return; 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]; int idx = 0; boolean hasPermutated = false; 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; hasPermutated = true; } } 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; hasPermutated = true; } } } // This ensure that the selection remains accurate when a shift occurs. final int selectedIndex = getSelectedIndex(); if (selectedIndex >= position && selectedIndex > -1) { // 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 + shift); 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 (hasPermutated) { selectedIndices.set(newSelectionLead, true); } else { select(newSelectionLead); } // removed due to RT-27185 // focus(newSelectionLead); } if (hasPermutated) { selectedIndicesSeq.callObservers( new NonIterableChange.SimplePermutationChange<Integer>( 0, selectedIndicesCardinality, perm, selectedIndicesSeq)); } } @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<Integer> 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<Integer> 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 = selectedIndicesSeq.indexOf(row); change = new NonIterableChange.GenericAddRemoveChange<>( changeIndex, changeIndex+1, previousSelectedIndices, selectedIndicesSeq); } selectedIndicesSeq.callObservers(change); } @Override public void select(int row) { if (row == -1) { clearSelection(); return; } if (row < 0 || row >= getItemCount()) { return; } boolean isSameRow = row == getSelectedIndex(); T currentItem = getSelectedItem(); T newItem = getModelItem(row); boolean isSameItem = newItem != null && newItem.equals(currentItem); boolean fireUpdatedItemEvent = isSameRow && ! isSameItem; startAtomic(); if (! selectedIndices.get(row)) { if (getSelectionMode() == SINGLE) { quietClearSelection(); } selectedIndices.set(row); } setSelectedIndex(row); focus(row); stopAtomic(); if (! isAtomic()) { int changeIndex = Math.max(0, selectedIndicesSeq.indexOf(row)); selectedIndicesSeq.callObservers(new NonIterableChange.SimpleAddChange<Integer>(changeIndex, changeIndex+1, selectedIndicesSeq)); } if (fireUpdatedItemEvent) { setSelectedItem(newItem); } } @Override public void select(T obj) { // if (getItemCount() <= 0) return; if (obj == null && getSelectionMode() == SelectionMode.SINGLE) { clearSelection(); return; } // We have no option but to iterate through the model and select the // first occurrence of the given object. Once we find the first one, we // don't proceed to select any others. Object rowObj = null; for (int i = 0, max = getItemCount(); i < max; i++) { rowObj = getModelItem(i); if (rowObj == null) continue; if (rowObj.equals(obj)) { if (isSelected(i)) { return; } if (getSelectionMode() == SINGLE) { quietClearSelection(); } select(i); return; } } // if we are here, we did not find the item in the entire data model. // Even still, we allow for this item to be set to the give object. // We expect that in concrete subclasses of this class we observe the // data model such that we check to see if the given item exists in it, // whilst SelectedIndex == -1 && SelectedItem != null. setSelectedIndex(-1); setSelectedItem(obj); } @Override public void selectIndices(int row, int... rows) { if (rows == null || rows.length == 0) { select(row); return; } /* * Performance optimisation - if multiple selection is disabled, only * process the end-most row index. */ int rowCount = getItemCount(); if (getSelectionMode() == SINGLE) { quietClearSelection(); for (int i = rows.length - 1; i >= 0; i--) { int index = rows[i]; if (index >= 0 && index < rowCount) { selectedIndices.set(index); select(index); break; } } if (selectedIndices.isEmpty()) { if (row > 0 && row < rowCount) { selectedIndices.set(row); select(row); } } selectedIndicesSeq.callObservers(new NonIterableChange.SimpleAddChange<Integer>(0, 1, selectedIndicesSeq)); } else { final List<Integer> actualSelectedRows = new ArrayList<Integer>(); int lastIndex = -1; if (row >= 0 && row < rowCount) { lastIndex = row; if (! selectedIndices.get(row)) { selectedIndices.set(row); actualSelectedRows.add(row); } } for (int i = 0; i < rows.length; i++) { int index = rows[i]; if (index < 0 || index >= rowCount) continue; lastIndex = index; if (! selectedIndices.get(index)) { selectedIndices.set(index); actualSelectedRows.add(index); } } if (lastIndex != -1) { setSelectedIndex(lastIndex); focus(lastIndex); setSelectedItem(getModelItem(lastIndex)); } // need to come up with ranges based on the actualSelectedRows, and // then fire the appropriate number of changes. We also need to // translate from a desired row to select to where that row is // represented in the selectedIndices list. For example, // we may have requested to select row 5, and the selectedIndices // list may therefore have the following: [1,4,5], meaning row 5 // is in position 2 of the selectedIndices list Collections.sort(actualSelectedRows); Change<Integer> change = createRangeChange(selectedIndicesSeq, actualSelectedRows, false); selectedIndicesSeq.callObservers(change); } } static Change<Integer> createRangeChange(final ObservableList<Integer> list, final List<Integer> addedItems, boolean splitChanges) { Change<Integer> change = new Change<Integer>(list) { private final int[] EMPTY_PERM = new int[0]; private final int addedSize = addedItems.size(); private boolean invalid = true; private int pos = 0; private int from = pos; private int to = pos; @Override public int getFrom() { checkState(); return from; } @Override public int getTo() { checkState(); return to; } @Override public List<Integer> getRemoved() { checkState(); return Collections.<Integer>emptyList(); } @Override protected int[] getPermutation() { checkState(); return EMPTY_PERM; } @Override public int getAddedSize() { return to - from; } @Override public boolean next() { if (pos >= addedSize) return false; // starting from pos, we keep going until the value is // not the next value int startValue = addedItems.get(pos++); from = list.indexOf(startValue); to = from + 1; int endValue = startValue; while (pos < addedSize) { int previousEndValue = endValue; endValue = addedItems.get(pos++); ++to; if (splitChanges && previousEndValue != (endValue - 1)) { break; } } if (invalid) { invalid = false; return true; } // we keep going until we've represented all changes! return splitChanges && pos < addedSize; } @Override public void reset() { invalid = true; pos = 0; to = 0; from = 0; } private void checkState() { if (invalid) { throw new IllegalStateException("Invalid Change state: next() must be called before inspecting the Change."); } } }; return change; } @Override public void selectAll() { if (getSelectionMode() == SINGLE) return; if (getItemCount() <= 0) return; final int rowCount = getItemCount(); final int focusedIndex = getFocusedIndex(); // set all selected indices to true clearSelection(); selectedIndices.set(0, rowCount, true); selectedIndicesSeq.callObservers(new NonIterableChange.SimpleAddChange<>(0, rowCount, selectedIndicesSeq)); if (focusedIndex == -1) { setSelectedIndex(rowCount - 1); focus(rowCount - 1); } else { setSelectedIndex(focusedIndex); focus(focusedIndex); } } @Override public void selectFirst() { if (getSelectionMode() == SINGLE) { quietClearSelection(); } if (getItemCount() > 0) { select(0); } } @Override public void selectLast() { if (getSelectionMode() == SINGLE) { quietClearSelection(); } int numItems = getItemCount(); if (numItems > 0 && getSelectedIndex() < numItems - 1) { select(numItems - 1); } } @Override public void clearSelection(int index) { if (index < 0) return; // 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); if (! wasEmpty && selectedIndices.isEmpty()) { clearSelection(); } if (!isAtomic()) { // we pass in (index, index) here to represent that nothing was added // in this change. selectedIndicesSeq.callObservers( new NonIterableChange.GenericAddRemoveChange<>(index, index, Collections.singletonList(index), selectedIndicesSeq)); } } @Override public void clearSelection() { List<Integer> removed = new BitSetReadOnlyUnbackedObservableList((BitSet) selectedIndices.clone()); quietClearSelection(); if (! isAtomic()) { setSelectedIndex(-1); focus(-1); selectedIndicesSeq.callObservers( new NonIterableChange.GenericAddRemoveChange<>(0, 0, removed, selectedIndicesSeq)); } } private void quietClearSelection() { selectedIndices.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; } @Override public boolean isEmpty() { return selectedIndices.isEmpty(); } @Override public void selectPrevious() { int focusIndex = getFocusedIndex(); if (getSelectionMode() == SINGLE) { quietClearSelection(); } if (focusIndex == -1) { select(getItemCount() - 1); } else if (focusIndex > 0) { select(focusIndex - 1); } } @Override public void selectNext() { int focusIndex = getFocusedIndex(); if (getSelectionMode() == SINGLE) { quietClearSelection(); } if (focusIndex == -1) { select(0); } else if (focusIndex != getItemCount() -1) { select(focusIndex + 1); } } /*********************************************************************** * * * Private implementation * * * **********************************************************************/ class BitSetReadOnlyUnbackedObservableList extends ReadOnlyUnbackedObservableList<Integer> { private final BitSet bitset; private int lastGetIndex = -1; private int lastGetValue = -1; 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; } } }
Ms-Dos/Windows
Unix
Write backup
jsp File Browser version 1.2 by
www.vonloesch.de