com.sun.javafx.scene.control.SelectedCellsMap Maven / Gradle / Ivy
/*
* Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javafx.scene.control;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.scene.control.TablePositionBase;
import java.util.*;
/**
* Implementation code used by the TableSelectionModel implementations. In short
* this code exists to speed up some common use cases which were incredibly
* slow in the old approach. The old approach essentially required a lot of
* iterating through the selectedCells list. The new approach is to keep this
* list for what it is good for (representing selection order primarily), and
* introduce a Map to speed up the slow parts - namely looking
* up whether a given row/column intersection is selected or not.
*
* Note that a map that contains an empty bitset is used to represent that the
* row is selected.
*
* Refer to RT-33442 for more information on this issue.
*/
// T == TablePosition
public abstract class SelectedCellsMap {
private final ObservableList selectedCells;
private final ObservableList sortedSelectedCells;
private final Map selectedCellBitSetMap;
public SelectedCellsMap(final ListChangeListener listener) {
selectedCells = FXCollections.observableArrayList();
sortedSelectedCells = new SortedList<>(selectedCells, (T o1, T o2) -> {
int result = o1.getRow() - o2.getRow();
return result == 0 ? (o1.getColumn() - o2.getColumn()) : result;
});
sortedSelectedCells.addListener(listener);
selectedCellBitSetMap = new TreeMap<>((o1, o2) -> o1.compareTo(o2));
}
public abstract boolean isCellSelectionEnabled();
public int size() {
return selectedCells.size();
}
public T get(int i) {
if (i < 0) {
return null;
}
return sortedSelectedCells.get(i);
}
public void add(T tp) {
final int row = tp.getRow();
final int columnIndex = tp.getColumn();
// update the bitset map
boolean isNewBitSet = false;
BitSet bitset;
if (! selectedCellBitSetMap.containsKey(row)) {
bitset = new BitSet();
selectedCellBitSetMap.put(row, bitset);
isNewBitSet = true;
} else {
bitset = selectedCellBitSetMap.get(row);
}
final boolean cellSelectionModeEnabled = isCellSelectionEnabled();
if (cellSelectionModeEnabled) {
if (columnIndex >= 0) {
boolean isAlreadySet = bitset.get(columnIndex);
if (!isAlreadySet) {
bitset.set(columnIndex);
// add into the list
selectedCells.add(tp);
}
} else {
// FIXME slow path (for now)
if (!selectedCells.contains(tp)) {
selectedCells.add(tp);
}
}
} else {
if (isNewBitSet) {
if (columnIndex >= 0) {
bitset.set(columnIndex);
}
selectedCells.add(tp);
}
}
}
public void addAll(Collection cells) {
// update bitset
for (T tp : cells) {
final int row = tp.getRow();
final int columnIndex = tp.getColumn();
// update the bitset map
BitSet bitset;
if (! selectedCellBitSetMap.containsKey(row)) {
bitset = new BitSet();
selectedCellBitSetMap.put(row, bitset);
} else {
bitset = selectedCellBitSetMap.get(row);
}
if (columnIndex < 0) {
continue;
}
bitset.set(columnIndex);
}
// add into the list
selectedCells.addAll(cells);
}
public void setAll(Collection cells) {
// update bitset
selectedCellBitSetMap.clear();
for (T tp : cells) {
final int row = tp.getRow();
final int columnIndex = tp.getColumn();
// update the bitset map
BitSet bitset;
if (! selectedCellBitSetMap.containsKey(row)) {
bitset = new BitSet();
selectedCellBitSetMap.put(row, bitset);
} else {
bitset = selectedCellBitSetMap.get(row);
}
if (columnIndex < 0) {
continue;
}
bitset.set(columnIndex);
}
// add into the list
selectedCells.setAll(cells);
}
public void remove(T tp) {
final int row = tp.getRow();
final int columnIndex = tp.getColumn();
// update the bitset map
if (selectedCellBitSetMap.containsKey(row)) {
BitSet bitset = selectedCellBitSetMap.get(row);
if (columnIndex >= 0) {
bitset.clear(columnIndex);
}
if (bitset.isEmpty()) {
selectedCellBitSetMap.remove(row);
}
}
// update list
selectedCells.remove(tp);
}
public void clear() {
// update bitset
selectedCellBitSetMap.clear();
// update list
selectedCells.clear();
}
public boolean isSelected(int row, int columnIndex) {
if (columnIndex < 0) {
return selectedCellBitSetMap.containsKey(row);
} else {
return selectedCellBitSetMap.containsKey(row) ? selectedCellBitSetMap.get(row).get(columnIndex) : false;
}
}
public int indexOf(T tp) {
return sortedSelectedCells.indexOf(tp);
}
public boolean isEmpty() {
return selectedCells.isEmpty();
}
public ObservableList getSelectedCells() {
return selectedCells;
}
}