blob: db0a43e22a4a24352ef97653d8c43ac0220f397f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2018 Original authors and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Original authors and others - initial API and implementation
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.selection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
import org.eclipse.nebula.widgets.nattable.coordinate.Range;
import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
/**
* Helper class to operate with selections.
*/
public class SelectionUtils {
/**
*
* @param withShiftMask
* flag to indicate whether the shift masked is active
* @param withControlMask
* flag to indicate whether the control masked is active
* @return <code>true</code> if both, shift and control mask, are not active
*/
public static boolean noShiftOrControl(boolean withShiftMask, boolean withControlMask) {
return !withShiftMask && !withControlMask;
}
/**
*
* @param withShiftMask
* flag to indicate whether the shift masked is active
* @param withControlMask
* flag to indicate whether the control masked is active
* @return <code>true</code> if both, shift and control mask are active
*/
public static boolean bothShiftAndControl(boolean withShiftMask, boolean withControlMask) {
return withShiftMask && withControlMask;
}
/**
*
* @param withShiftMask
* flag to indicate whether the shift masked is active
* @param withControlMask
* flag to indicate whether the control masked is active
* @return <code>true</code> if only the control mask is active
*/
public static boolean isControlOnly(boolean withShiftMask, boolean withControlMask) {
return !withShiftMask && withControlMask;
}
/**
*
* @param withShiftMask
* flag to indicate whether the shift masked is active
* @param withControlMask
* flag to indicate whether the control masked is active
* @return <code>true</code> if only the shift mask is active
*/
public static boolean isShiftOnly(boolean withShiftMask, boolean withControlMask) {
return withShiftMask && !withControlMask;
}
/**
* Test if the numbers in the given array are consecutive. If there are
* duplicates or gaps in the array, <code>false</code> will be returned.
*
* @param pos
* the array of numbers to check
* @return <code>true</code> if the numbers are consecutive,
* <code>false</code> if there are duplicates or gaps.
*
* @since 1.4
*/
public static boolean isConsecutive(int[] pos) {
Arrays.sort(pos);
for (int i = 1; i < pos.length; i++) {
if (pos[i - 1] + 1 != pos[i]) {
return false;
}
}
return true;
}
/**
* Checks whether the selected region tracked by the given
* {@link SelectionLayer} is consecutive or not.
*
* @param selectionLayer
* The {@link SelectionLayer} that tracks the selection.
* @return <code>true</code> if the current selection is consecutive,
* <code>false</code> if not.
*
* @since 1.4
*/
public static boolean hasConsecutiveSelection(SelectionLayer selectionLayer) {
if (selectionLayer != null
&& SelectionUtils.isConsecutive(selectionLayer.getSelectedColumnPositions())) {
Map<Integer, Set<Integer>> positions = new HashMap<Integer, Set<Integer>>();
int column = -1;
int row = -1;
// collect the selection information
for (PositionCoordinate coord : selectionLayer.getSelectedCellPositions()) {
Set<Integer> rows = positions.get(coord.columnPosition);
if (rows == null) {
rows = new LinkedHashSet<Integer>();
positions.put(coord.columnPosition, rows);
}
rows.add(coord.rowPosition);
column = Math.max(column, coord.columnPosition);
row = Math.max(row, coord.rowPosition);
}
// check if the selected region is a rectangle
// every row collection for each column needs to have the same size
// and the same content
Set<Integer> previous = null;
for (Set<Integer> rows : positions.values()) {
if (previous != null && !previous.equals(rows)) {
return false;
}
previous = rows;
}
// test if rows are consecutive
if (previous != null) {
int[] rowPositions = new int[previous.size()];
int i = 0;
for (Integer rowPos : previous) {
rowPositions[i++] = rowPos;
}
if (SelectionUtils.isConsecutive(rowPositions)) {
return true;
}
}
}
return false;
}
/**
* Returns the bottom right cell of a selected region. Will only return an
* ILayerCell if the selected region is consecutive. Otherwise
* <code>null</code> will be returned.
*
* @param selectionLayer
* The {@link SelectionLayer} needed to determine the selection.
*
* @return The bottom right cell of a selected region or <code>null</code>
* if the selected region is not consecutive.
*
* @since 1.4
*/
public static ILayerCell getBottomRightCellInSelection(SelectionLayer selectionLayer) {
if (hasConsecutiveSelection(selectionLayer)) {
int column = -1;
int row = -1;
for (PositionCoordinate coord : selectionLayer.getSelectedCellPositions()) {
column = Math.max(column, coord.columnPosition);
row = Math.max(row, coord.rowPosition);
}
return selectionLayer.getCellByPosition(column, row);
}
return null;
}
/**
* Inspects the current selection on the given {@link SelectionLayer} and
* returns a list of the corresponding list item objects. Uses the
* {@link IRowDataProvider} to be able to determine the row objects per
* selected row position.
*
* @param selectionLayer
* The {@link SelectionLayer} to retrieve the selected row
* indexes from.
* @param rowDataProvider
* The {@link IRowDataProvider} to retrieve the object for the
* row index.
* @param fullySelectedRowsOnly
* Flag to determine if only fully selected rows should be taken
* into account.
* @return The list of all objects that are currently marked as selected.
* Never <code>null</code>.
*
* @since 1.4
*/
public static <T> List<T> getSelectedRowObjects(
SelectionLayer selectionLayer,
IRowDataProvider<T> rowDataProvider,
boolean fullySelectedRowsOnly) {
List<RowObjectIndexHolder<T>> rows = new ArrayList<RowObjectIndexHolder<T>>();
if (selectionLayer != null) {
if (fullySelectedRowsOnly) {
for (int rowPosition : selectionLayer.getFullySelectedRowPositions()) {
addToSelection(rows, rowPosition, selectionLayer, rowDataProvider);
}
} else {
Set<Range> rowRanges = selectionLayer.getSelectedRowPositions();
for (Range rowRange : rowRanges) {
for (int rowPosition = rowRange.start; rowPosition < rowRange.end; rowPosition++) {
addToSelection(rows, rowPosition, selectionLayer, rowDataProvider);
}
}
}
}
Collections.sort(rows);
List<T> rowObjects = new ArrayList<T>(rows.size());
for (RowObjectIndexHolder<T> holder : rows) {
rowObjects.add(holder.getRow());
}
return rowObjects;
}
private static <T> void addToSelection(
List<RowObjectIndexHolder<T>> rows,
int rowPosition,
SelectionLayer selectionLayer,
IRowDataProvider<T> rowDataProvider) {
int rowIndex = selectionLayer.getRowIndexByPosition(rowPosition);
if (rowIndex >= 0 && rowIndex < rowDataProvider.getRowCount()) {
T rowObject = rowDataProvider.getRowObject(rowIndex);
rows.add(new RowObjectIndexHolder<T>(rowIndex, rowObject));
}
}
}