/*******************************************************************************
* Copyright (c) 2014 Jonas Hugo, Markus Wahl.
* 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:
* Jonas Hugo <Jonas.Hugo@jeppesen.com>,
* Markus Wahl <Markus.Wahl@jeppesen.com> - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - Bug 447396
* Dirk Fauth <dirk.fauth@googlemail.com> - made inner classes static for better generic handling
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.selection.preserve;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* The selected cells of columns and rows
*
* @param <T>
* the type of object underlying each row
*/
class Selections<T> {
/**
* A map for looking up rows given their row IDs
*/
private Map<Serializable, Row<T>> selectedRows = new HashMap<Serializable, Row<T>>();
/**
* A map for looking up columns given their column positions
*/
private Map<Integer, Column> selectedColumns = new HashMap<Integer, Column>();
/**
* Select the cell at the intersection of the specified row and column.
*
* @param rowId
* @param rowObject
* row object with the row rowId
* @param columnPosition
*/
void select(Serializable rowId, T rowObject, int columnPosition) {
Row<T> row = retrieveRow(rowId, rowObject);
row.addItem(columnPosition);
Column column = retrieveColumn(columnPosition);
column.addItem(rowId);
}
/**
* Removes the selection of the cell at the intersection of the specified
* row and column.
*
* @param rowId
* @param columnPosition
*/
void deselect(Serializable rowId, int columnPosition) {
Row<T> row = getSelectedColumns(rowId);
if (row != null) {
row.removeItem(columnPosition);
if (!row.hasSelection()) {
this.selectedRows.remove(rowId);
}
}
Column column = getSelectedRows(columnPosition);
if (column != null) {
column.removeItem(rowId);
if (!column.hasSelection()) {
this.selectedColumns.remove(columnPosition);
}
}
}
/**
* Removes the selection of all cells for the specified row id.
*
* @param rowId
*/
void deselectRow(Serializable rowId) {
Row<T> row = getSelectedColumns(rowId);
if (row != null) {
row.clearItems();
this.selectedRows.remove(rowId);
}
Collection<Integer> toRemove = new HashSet<Integer>();
for (Map.Entry<Integer, Column> entry : this.selectedColumns.entrySet()) {
entry.getValue().removeItem(rowId);
if (!entry.getValue().hasSelection()) {
toRemove.add(entry.getKey());
}
}
for (Integer key : toRemove) {
this.selectedColumns.remove(key);
}
}
/**
* Removes the selection of all cells for the specified column.
*
* @param columnPosition
*/
void deselectColumn(int columnPosition) {
Column column = getSelectedRows(columnPosition);
if (column != null) {
column.clearItems();
this.selectedColumns.remove(columnPosition);
}
Collection<Serializable> toRemove = new HashSet<Serializable>();
for (Map.Entry<Serializable, Row<T>> entry : this.selectedRows.entrySet()) {
entry.getValue().removeItem(columnPosition);
if (!entry.getValue().hasSelection()) {
toRemove.add(entry.getKey());
}
}
for (Serializable key : toRemove) {
this.selectedRows.remove(key);
}
}
void updateColumnsForRemoval(int columnPosition) {
// find maximum selected column
int maxColumn = 0;
for (Integer pos : this.selectedColumns.keySet()) {
maxColumn = Math.max(maxColumn, pos);
}
for (int i = columnPosition + 1; i <= maxColumn; i++) {
Column column = this.selectedColumns.get(i);
if (column != null) {
this.selectedColumns.put(i - 1, new Column(i - 1));
this.selectedColumns.remove(i);
// also update the row references
for (Row<T> row : this.selectedRows.values()) {
Collection<Integer> toRemove = new HashSet<Integer>();
Collection<Integer> toAdd = new HashSet<Integer>();
for (Integer col : row.getItems()) {
if (col <= i) {
toRemove.add(i);
toAdd.add(i - 1);
}
}
row.getItems().removeAll(toRemove);
row.getItems().addAll(toAdd);
}
}
}
}
void updateColumnsForAddition(int columnPosition) {
// find maximum selected column
int maxColumn = 0;
for (Integer pos : this.selectedColumns.keySet()) {
maxColumn = Math.max(maxColumn, pos);
}
for (int i = maxColumn; i >= columnPosition; i--) {
Column column = this.selectedColumns.get(i);
if (column != null) {
this.selectedColumns.put(i + 1, new Column(i + 1));
this.selectedColumns.remove(i);
// also update the row references
for (Row<T> row : this.selectedRows.values()) {
Collection<Integer> toRemove = new HashSet<Integer>();
Collection<Integer> toAdd = new HashSet<Integer>();
for (Integer col : row.getItems()) {
if (col >= i) {
toRemove.add(i);
toAdd.add(i + 1);
}
}
row.getItems().removeAll(toRemove);
row.getItems().addAll(toAdd);
}
}
}
}
/**
* Removes all cell selections.
*/
void clear() {
this.selectedRows.clear();
this.selectedColumns.clear();
}
/**
* Retrieves all rows that have selected cells.
*
* @return all rows that have selected cells
*/
Collection<Row<T>> getRows() {
return this.selectedRows.values();
}
/**
* Retrieves the column positions of all columns with selected cells. The
* positions are naturally sorted.
*
* @return all columns positions with selected cells
*/
List<Integer> getColumnPositions() {
List<Integer> keys = new ArrayList<Integer>(this.selectedColumns.keySet());
Collections.sort(keys);
return keys;
}
/**
* Retrieves the selected rows of a column
*
* @param columnPosition
* column for retrieving selected rows
* @return selected rows of columnPosition, or null if no selected rows in
* that column
*/
Column getSelectedRows(int columnPosition) {
return this.selectedColumns.get(columnPosition);
}
/**
* Retrieves the selected columns of a row
*
* @param rowId
* row ID for retrieving selected columns
* @return selected columns of rowId, or null if no selected columns in that
* row
*/
Row<T> getSelectedColumns(Serializable rowId) {
return this.selectedRows.get(rowId);
}
/**
* Retrieves all selected cell positions expressed in row object and column
* position. The size of the collection is zero when there are no selected
* cells.
*
* @return all selected cell positions
*/
Collection<CellPosition<T>> getSelections() {
ArrayList<CellPosition<T>> selectedCells = new ArrayList<CellPosition<T>>();
for (Row<T> row : this.selectedRows.values()) {
for (Integer columnPosition : row.getItems()) {
CellPosition<T> cell = new CellPosition<T>(row.getRowObject(), columnPosition);
selectedCells.add(cell);
}
}
return selectedCells;
}
/**
* Determines whether a cell is selected
*
* @param rowId
* row ID of the inspected cell
* @param columnPosition
* column position of the inspected cell
* @return whether the specified cell is selected
*/
boolean isSelected(Serializable rowId, int columnPosition) {
if (isRowSelected(rowId)) {
return getSelectedColumns(rowId).contains(columnPosition);
} else {
return false;
}
}
/**
* Determines whether a row contains a selected cell
*
* @param rowId
* row ID to inspect
* @return whether the specified row contains a selected cell
*/
boolean isRowSelected(Serializable rowId) {
return this.selectedRows.containsKey(rowId);
}
/**
* Determines whether there are selected cells
*
* @return whether there are selected cells
*/
boolean isEmpty() {
return this.selectedRows.isEmpty();
}
/**
* Retrieves a collection of selected columns for a row. The row is
* specified by row ID, but row object is needed when the row does not
* already have any other selected cells.
*
* @param rowId
* row to retrieve columns for
* @param rowObject
* row object with the row rowId
* @return a collection of selected columns for the row
*/
private Row<T> retrieveRow(Serializable rowId, T rowObject) {
Row<T> row = getSelectedColumns(rowId);
if (row == null) {
row = new Row<T>(rowId, rowObject);
this.selectedRows.put(rowId, row);
}
return row;
}
/**
* Retrieves a collection of selected rows for a column.
*
* @param columnPosition
* column to retrieve columns for
* @return a collection of selected rows for a column
*/
private Column retrieveColumn(int columnPosition) {
Column column = getSelectedRows(columnPosition);
if (column == null) {
column = new Column(columnPosition);
this.selectedColumns.put(columnPosition, column);
}
return column;
}
/**
* A collection of selected columns for a row. Row is a Line<Serializable,
* Integer> where <Serializable> denotes the row ID of the row and <Integer>
* denotes the type of the selected columns (column positions).
*/
static class Row<R> extends Line<Serializable, Integer> {
/**
* The underlying row object
*/
private final R rowObject;
/**
* Creates a row with the specified row
*
* @param rowId
* ID of the row
* @param rowObject
* underlying row object
*/
Row(Serializable rowId, R rowObject) {
super(rowId);
this.rowObject = rowObject;
}
/**
* Retrieves the underlying row object
*
* @return the underlying row object
*/
R getRowObject() {
return this.rowObject;
}
}
/**
* A collection of selected rows for a column. Column is a Line<Integer,
* Serializable> where <Integer> denotes the column position of the column
* and <Serializable> denotes the type of the selected rows (row ID).
*/
static class Column extends Line<Integer, Serializable> {
/**
* Creates a column with the specified column
*
* @param columnPosition
* position of the column
*/
Column(Integer columnPosition) {
super(columnPosition);
}
}
/**
* A collection of selected items in a line.
*
* @param <I>
* the type of the line identifier
* @param <S>
* the type of the selected items
*/
static class Line<I, S> {
/**
* Identifying the line
*/
private final I lineId;
/**
* The selected items
*/
private HashSet<S> content = new HashSet<S>();
/**
* Creates a line with the specified ID
*
* @param lineId
* identifying the line
*/
Line(I lineId) {
this.lineId = lineId;
}
/**
* Retrieves the selected items of the line
*
* @return the selected items
*/
Collection<S> getItems() {
return this.content;
}
/**
* Adds an item to the selected items
*
* @param item
* item to add
*/
void addItem(S item) {
this.content.add(item);
}
/**
* Removes an item from the selected items
*
* @param item
* item to remove
*/
void removeItem(S item) {
this.content.remove(item);
}
/**
* Clears the selected items.
*/
void clearItems() {
this.content.clear();
}
/**
* Determines whether a certain item is selected in the line
*
* @param item
* item to look for
* @return whether the item is selected in the line
*/
boolean contains(S item) {
return this.content.contains(item);
}
/**
* Determines whether the line has any selections
*
* @return whether the line has any selections
*/
boolean hasSelection() {
return !this.content.isEmpty();
}
/**
* Retrieves the line identifier
*
* @return the line identifier
*/
I getId() {
return this.lineId;
}
}
/**
* Position of a cell expressed in row object and column position
*
* @param <T>
* the type of row object
*/
static class CellPosition<T> {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.columnPosition;
result = prime * result
+ ((this.rowObject == null) ? 0 : this.rowObject.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
@SuppressWarnings("unchecked")
CellPosition<T> other = (CellPosition<T>) obj;
if (this.columnPosition != other.columnPosition)
return false;
if (this.rowObject == null) {
if (other.rowObject != null)
return false;
} else if (!this.rowObject.equals(other.rowObject))
return false;
return true;
}
/**
* Column position
*/
private final int columnPosition;
/**
* Row object
*/
private final T rowObject;
/**
* Creates a cell position
*
* @param rowObject
* row object
* @param columnPosition
* column position
*/
CellPosition(T rowObject, int columnPosition) {
this.rowObject = rowObject;
this.columnPosition = columnPosition;
}
/**
* Retrieves the row object
*
* @return the row object
*/
T getRowObject() {
return this.rowObject;
}
/**
* Retrieves the column position
*
* @return the column position
*/
Integer getColumnPosition() {
return this.columnPosition;
}
}
}