diff options
author | unknown | 2014-05-26 12:42:50 +0000 |
---|---|---|
committer | Markus Wahl | 2014-06-17 13:36:37 +0000 |
commit | 5b57355af48fbd5ab31e29e5acece9df7b716eac (patch) | |
tree | 05aed943d9e9ec2843d6cd7a35d5b415b59280bf | |
parent | 97a2ad3614bc23a473e4b6a6403c25720f1321d3 (diff) | |
download | org.eclipse.nebula.widgets.nattable-5b57355af48fbd5ab31e29e5acece9df7b716eac.tar.gz org.eclipse.nebula.widgets.nattable-5b57355af48fbd5ab31e29e5acece9df7b716eac.tar.xz org.eclipse.nebula.widgets.nattable-5b57355af48fbd5ab31e29e5acece9df7b716eac.zip |
Fix Bug 434608. SelectionLayer Delegate Markers to Model.
SelectionLayer delegate markers to model iff model is an
IMarkerSelectionModel. Add getters and setters for marker
fields of SelectionLayer. Add implementation of
IMarkerSelectionModel: PreserveSelectionModel<T>.
Signed-off-by: Markus Wahl <Markus.Wahl@jeppesen.com>
Change-Id: Ib472a43a18d98b95708ac282a07e131f1fb2e0ef
13 files changed, 2646 insertions, 144 deletions
diff --git a/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/SelectionLayerTest.java b/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/SelectionLayerTest.java index 76f2d89a..d8982c8e 100644 --- a/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/SelectionLayerTest.java +++ b/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/SelectionLayerTest.java @@ -7,13 +7,29 @@ * * Contributors: * Original authors and others - initial API and implementation + * Jonas Hugo <Jonas.Hugo@jeppesen.com>, + * Markus Wahl <Markus.Wahl@jeppesen.com> - Test delegation of markers to + * model iff model is an IMarkerSelectionModel. Test getters and setters + * for marker fields. ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.selection; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; -import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import java.util.List; +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.test.LayerAssert; import org.eclipse.nebula.widgets.nattable.test.fixture.TestLayer; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; import org.junit.Before; import org.junit.Test; @@ -21,7 +37,9 @@ public class SelectionLayerTest { private TestLayer testLayer; private SelectionLayer selectionLayer; - + + private StubbedMarkerSelectionModel markerSelectionModel = new StubbedMarkerSelectionModel(); + @Before public void setup() { String columnInfo = "0:0;100 | 1:1;100 | 2:2;100 | 3:3;100"; @@ -42,5 +60,361 @@ public class SelectionLayerTest { public void testIdentityLayerTransform() { LayerAssert.assertLayerEquals(testLayer, selectionLayer); } - + + // Clear + + @Test + public void testClearAllClearsAllMarkers() throws Exception { + selectionLayer.selectAll(); + + selectionLayer.clear(); + + assertNull(selectionLayer.getLastSelectedCellPosition()); + assertEquals(0, selectionLayer.getLastSelectedRegion().width); + assertEquals(0, selectionLayer.getLastSelectedRegion().height); + + assertEquals(SelectionLayer.NO_SELECTION, selectionLayer.getSelectionAnchor().columnPosition); + assertEquals(SelectionLayer.NO_SELECTION, selectionLayer.getSelectionAnchor().rowPosition); + } + + @Test + public void testClearSingleCellClearsNoMarkers() throws Exception { + selectionLayer.selectAll(); + + selectionLayer.clearSelection(1, 1); + + assertNotNull(selectionLayer.getLastSelectedCellPosition()); + assertTrue(selectionLayer.getLastSelectedRegion().width > 0); + assertTrue(selectionLayer.getLastSelectedRegion().height > 0); + + assertFalse(selectionLayer.getSelectionAnchor().columnPosition == SelectionLayer.NO_SELECTION); + assertFalse(selectionLayer.getSelectionAnchor().rowPosition == SelectionLayer.NO_SELECTION); + } + + @Test + public void testClearAnchorRectangleClearsOnlyAnchor() throws Exception { + selectionLayer.selectAll(); + + selectionLayer.clearSelection(new Rectangle(0, 0, 1, 1)); + + assertNotNull(selectionLayer.getLastSelectedCellPosition()); + assertTrue(selectionLayer.getLastSelectedRegion().width > 0); + assertTrue(selectionLayer.getLastSelectedRegion().height > 0); + + assertEquals(SelectionLayer.NO_SELECTION, selectionLayer.getSelectionAnchor().columnPosition); + assertEquals(SelectionLayer.NO_SELECTION, selectionLayer.getSelectionAnchor().rowPosition); + } + + @Test + public void testClearOutsideAnchorRectangleClearsNoMarkers() throws Exception { + selectionLayer.selectAll(); + + selectionLayer.clearSelection(new Rectangle(1, 1, 1, 1)); + + assertFalse(selectionLayer.getSelectionAnchor().columnPosition == SelectionLayer.NO_SELECTION); + assertFalse(selectionLayer.getSelectionAnchor().rowPosition == SelectionLayer.NO_SELECTION); + } + + // Last Selected Region + + @Test + public void testGetLastSelectedRegionDoesNotDelegateToModel() throws Exception { + Rectangle lastSelectedRegion = new Rectangle(22, 22, 22, 22); + selectionLayer.lastSelectedRegion = lastSelectedRegion; + + assertSame(lastSelectedRegion, selectionLayer.getLastSelectedRegion()); + } + + @Test + public void testGetLastSelectedRegionDelegatesToAnchorModel() throws Exception { + selectionLayer.setSelectionModel(markerSelectionModel); + + Rectangle lastSelectedRegion = new Rectangle(22, 22, 22, 22); + markerSelectionModel.setLastSelectedRegion(lastSelectedRegion); + + assertEquals(lastSelectedRegion, selectionLayer.getLastSelectedRegion()); + } + + @Test + public void testSetLastSelectedRegionDelegatesToAnchorModel() throws Exception { + selectionLayer.setSelectionModel(markerSelectionModel); + + Rectangle region = new Rectangle(23454234, 123123, 12, 5); + selectionLayer.setLastSelectedRegion(region); + + assertSame(region, markerSelectionModel.getLastSelectedRegion()); + assertNull(selectionLayer.lastSelectedRegion); + } + + @Test + public void testSetLastSelectedRegionDoesNotDelegateToModel() throws Exception { + Rectangle region = new Rectangle(23454234, 123123, 12, 5); + selectionLayer.setLastSelectedRegion(region); + + assertSame(region, selectionLayer.lastSelectedRegion); + } + + @Test + public void testSetLastSelectedRegionPreservesNULL() throws Exception { + selectionLayer.setLastSelectedRegion(null); + + assertNull(selectionLayer.lastSelectedRegion); + } + + @Test + public void testSetLastSelectedRegionFieldsDelegatesToAnchorModel() throws Exception { + selectionLayer.setSelectionModel(markerSelectionModel); + + Rectangle region = new Rectangle(23454234, 123123, 12, 5); + selectionLayer.setLastSelectedRegion(region.x, region.y, region.width, region.height); + + assertEquals(region, markerSelectionModel.getLastSelectedRegion()); + assertNull(selectionLayer.lastSelectedRegion); + } + + @Test + public void testSetLastSelectedRegionFieldsDoesNotDelegateToModel() throws Exception { + selectionLayer.selectAll(); + + Rectangle existingRegion = selectionLayer.lastSelectedRegion; + + Rectangle region = new Rectangle(23454234, 123123, 12, 5); + selectionLayer.setLastSelectedRegion(region.x, region.y, region.width, region.height); + + assertEquals(region, selectionLayer.lastSelectedRegion); + assertSame(existingRegion, selectionLayer.lastSelectedRegion); + } + + // Selection Anchor + + @Test + public void testGetAnchorDoesNotDelegateToModel() throws Exception { + PositionCoordinate existingAnchor = selectionLayer.selectionAnchor; + + assertSame(existingAnchor, selectionLayer.getSelectionAnchor()); + } + + @Test + public void testGetAnchorDelegatesToAnchorModel() throws Exception { + selectionLayer.setSelectionModel(markerSelectionModel); + + Point anchor = new Point(5, 7); + markerSelectionModel.setSelectionAnchor(anchor); + + assertEquals(anchor.x, selectionLayer.getSelectionAnchor().columnPosition); + assertEquals(anchor.y, selectionLayer.getSelectionAnchor().rowPosition); + } + + @Test + public void testSetSelectionAnchorDelegatesToAnchorModel() throws Exception { + selectionLayer.setSelectionModel(markerSelectionModel); + + selectionLayer.setSelectionAnchor(456, 8); + + assertEquals(456, markerSelectionModel.getSelectionAnchor().x); + assertEquals(8, markerSelectionModel.getSelectionAnchor().y); + assertFalse(SelectionLayer.hasSelection(selectionLayer.selectionAnchor)); + } + + @Test + public void testSetSelectionAnchorDoesNotDelegateToModel() throws Exception { + selectionLayer.selectAll(); + + selectionLayer.setSelectionAnchor(456, 8); + + assertEquals(456, selectionLayer.selectionAnchor.columnPosition); + assertEquals(8, selectionLayer.selectionAnchor.rowPosition); + } + + // Last Selected Cell + + @Test + public void testSetLastSelectedCellDelegatesToAnchorModel() throws Exception { + selectionLayer.setSelectionModel(markerSelectionModel); + + selectionLayer.setLastSelectedCell(456, 8); + + assertEquals(456, markerSelectionModel.getLastSelectedCell().x); + assertEquals(8, markerSelectionModel.getLastSelectedCell().y); + assertFalse(SelectionLayer.hasSelection(selectionLayer.lastSelectedCell)); + } + + @Test + public void testSetLastSelectedCellDoesNotDelegateToModel() throws Exception { + selectionLayer.selectAll(); + + selectionLayer.setLastSelectedCell(456, 8); + + assertEquals(456, selectionLayer.lastSelectedCell.columnPosition); + assertEquals(8, selectionLayer.lastSelectedCell.rowPosition); + } + + @Test + public void testGetLastSelectedCellDoesNotDelegateToModel() throws Exception { + selectionLayer.selectAll(); + PositionCoordinate existingSelectedCell = selectionLayer.lastSelectedCell; + + assertSame(existingSelectedCell, selectionLayer.getLastSelectedCell()); + } + + @Test + public void testGetLastSelectedCellDelegatesToAnchorModel() throws Exception { + selectionLayer.setSelectionModel(markerSelectionModel); + + Point lastSelected = new Point(5, 7); + markerSelectionModel.setLastSelectedCell(lastSelected); + + assertEquals(lastSelected.x, selectionLayer.getLastSelectedCell().columnPosition); + assertEquals(lastSelected.y, selectionLayer.getLastSelectedCell().rowPosition); + } + + @Test + public void testGetLastSelectedCellPosition() throws Exception { + selectionLayer.selectAll(); + PositionCoordinate existingSelectedCell = selectionLayer.getLastSelectedCell(); + + assertSame(existingSelectedCell, selectionLayer.getLastSelectedCellPosition()); + assertNotNull(existingSelectedCell); + } + + @Test + public void testGetLastSelectedCellPositionReturnsNullWhenUnselected() throws Exception { + assertNull(selectionLayer.getLastSelectedCellPosition()); + } + + public class StubbedMarkerSelectionModel implements IMarkerSelectionModel { + + private Rectangle lastSelectedRegion = new Rectangle(0, 0, 0, 0); + private Point anchor; + private Point lastSelectedCell; + + @Override + public boolean isMultipleSelectionAllowed() { + return false; + } + + @Override + public void setMultipleSelectionAllowed(boolean multipleSelectionAllowed) { + } + + @Override + public void addSelection(int columnPosition, int rowPosition) { + } + + @Override + public void addSelection(Rectangle range) { + } + + @Override + public void clearSelection() { + } + + @Override + public void clearSelection(int columnPosition, int rowPosition) { + } + + @Override + public void clearSelection(Rectangle removedSelection) { + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public List<Rectangle> getSelections() { + return null; + } + + @Override + public boolean isCellPositionSelected(int columnPosition, int rowPosition) { + return false; + } + + @Override + public int[] getSelectedColumnPositions() { + return null; + } + + @Override + public boolean isColumnPositionSelected(int columnPosition) { + return false; + } + + @Override + public int[] getFullySelectedColumnPositions(int columnHeight) { + return null; + } + + @Override + public boolean isColumnPositionFullySelected(int columnPosition, int columnHeight) { + return false; + } + + @Override + public int getSelectedRowCount() { + return 0; + } + + @Override + public Set<Range> getSelectedRowPositions() { + return null; + } + + @Override + public boolean isRowPositionSelected(int rowPosition) { + return false; + } + + @Override + public int[] getFullySelectedRowPositions(int rowWidth) { + return null; + } + + @Override + public boolean isRowPositionFullySelected(int rowPosition, int rowWidth) { + return false; + } + + @Override + public Point getSelectionAnchor() { + return anchor; + } + + @Override + public Point getLastSelectedCell() { + return lastSelectedCell; + } + + @Override + public Rectangle getLastSelectedRegion() { + return lastSelectedRegion; + } + + @Override + public void setSelectionAnchor(Point anchor) { + this.anchor = anchor; + } + + @Override + public void setLastSelectedCell(Point lastSelectedCell) { + this.lastSelectedCell = lastSelectedCell; + } + + @Override + public void setLastSelectedRegion(Rectangle region) { + this.lastSelectedRegion = region; + } + + @Override + public void setLastSelectedRegion(int x, int y, int width, int height) { + this.lastSelectedRegion.x = x; + this.lastSelectedRegion.y = y; + this.lastSelectedRegion.width = width; + this.lastSelectedRegion.height = height; + } + + } + } diff --git a/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/preserve/PreserveSelectionModelTest.java b/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/preserve/PreserveSelectionModelTest.java new file mode 100644 index 00000000..ebf7283e --- /dev/null +++ b/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/preserve/PreserveSelectionModelTest.java @@ -0,0 +1,719 @@ +/******************************************************************************* + * 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 test + ******************************************************************************/ +package org.eclipse.nebula.widgets.nattable.selection.preserve; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; + +import org.apache.commons.lang.NotImplementedException; +import org.eclipse.nebula.widgets.nattable.coordinate.Range; +import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider; +import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor; +import org.eclipse.nebula.widgets.nattable.layer.DataLayer; +import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.layer.cell.LayerCell; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.selection.preserve.PreserveSelectionModel; +import org.eclipse.nebula.widgets.nattable.selection.preserve.Selections.CellPosition; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.junit.Before; +import org.junit.Test; + +public class PreserveSelectionModelTest { + + private TestSelectionLayer selectionLayer; + private TestRowDataProvider rowDataProvider; + private TestRowIdAccessor rowIdAccessor; + + /** + * Each row consist of an array of String, one String for each column, i e each cell is a + * String. + */ + private PreserveSelectionModel<String[]> testee; + + private static String[] indexRow4 = new String[] { "row 4 hallo0", "hallo2", "hallo3" }; + private static String[] indexRow5 = new String[] { "row 5 hallo0", "hallo2", "hallo3" }; + private static String[] indexRow6 = new String[] { "row 6 hallo0", "hallo2", "hallo3" }; + + private int rowCount = 7, columnCount = 3; + + private TestCell[][] cells = new TestCell[7][3]; + + @Before + public void setUp() throws Exception { + + rowDataProvider = new TestRowDataProvider(); + rowIdAccessor = new TestRowIdAccessor(); + selectionLayer = new TestSelectionLayer(new DataLayer(rowDataProvider)); + /* + * row 0 in the immediate table is row 4 in the underlying virtual table coordinate + * system: + * + * row 0: A B C + * + * row 1: _ D E + * + * row 2: _ _ F + */ + mockCells(); + + testee = new PreserveSelectionModel<String[]>(selectionLayer, rowDataProvider, rowIdAccessor); + } + + /** + * row 0 in the immediate table is row 4 in the underlying virtual table coordinate system: + * + * row 0: A B C + * + * row 1: _ D E + * + * row 2: _ _ F + */ + private void mockCells() { + for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { + for (int columnIndex = 0; columnIndex < cells[rowIndex].length; columnIndex++) { + cells[rowIndex][columnIndex] = mockCell(columnIndex, rowIndex); + } + } + } + + private TestCell mockCell(int columnIndex, int rowIndex) { + int rowPosition = Math.max(rowIndex - 4, -1); + int columnPosition = columnIndex; + if (rowPosition != -1) { + return new TestCell(columnPosition, rowPosition); + } + return new TestCell(); + } + + @Test + public void Multiple_Selection_Is_Enabled_By_Default() { + assertTrue(testee.isMultipleSelectionAllowed()); + } + + @Test + public void Disabling_Multiple_Selection_Is_Communicated() { + testee.setMultipleSelectionAllowed(false); + + assertFalse(testee.isMultipleSelectionAllowed()); + } + + @Test + public void Disabling_Multiple_Selection_Is_Supported() { + testee.setMultipleSelectionAllowed(false); + testee.addSelection(0, 0); + testee.addSelection(2, 0); + + assertFalse(isCellSelected(0, 0)); + assertTrue(isCellSelected(2, 0)); + } + + @Test + public void Multiple_Selection_Is_Supported() { + testee.addSelection(0, 0); + testee.addSelection(2, 0); + + assertTrue(isCellSelected(0, 0)); + assertTrue(isCellSelected(2, 0)); + } + + private boolean isCellSelected(int columnPosition, int rowPosition) { + boolean singularVersion = testee.isCellPositionSelected(columnPosition, rowPosition); + boolean rectangleVersion = false; + for (Rectangle selection : testee.getSelections()) { + if (selection.intersects(columnPosition, rowPosition, 1, 1)) { + rectangleVersion = true; + } + } + return singularVersion && rectangleVersion; + } + + @Test + public void getSelections_Ignores_Invisible_Rows() { + // A is on row 0: + testee.addSelection(0, 0); + + // D is not on row 0: + testee.addSelection(1, 1); + + // Scroll away so that index row 4 is not visible: + selectionLayer.scrollOffset = 5; + + assertEquals(1, testee.getSelections().size()); + assertEquals(new Rectangle(1, 0, 1, 1), testee.getSelections().iterator().next()); + } + + @Test + public void Add_Selection_By_Rectangle_Is_Supported() { + testee.addSelection(new Rectangle(1, 0, 2, 2)); + + assertTrue(isCellSelected(1, 0)); + assertTrue(isCellSelected(1, 1)); + assertTrue(isCellSelected(2, 0)); + assertTrue(isCellSelected(2, 1)); + + assertFalse(isCellSelected(0, 0)); + assertFalse(isCellSelected(2, 2)); + } + + @Test + public void Adding_Infinite_Row_Selection_Is_Truncated_Before_Stored() { + columnCount = 2; + testee.addSelection(new Rectangle(0, 0, Integer.MAX_VALUE, 1)); + + assertTrue(isCellSelected(0, 0)); + assertTrue(isCellSelected(1, 0)); + assertFalse(isCellSelected(2, 0)); + } + + @Test + public void Adding_Infinite_Column_Selection_Is_Truncated_Before_Stored() { + rowCount = 6; + testee.addSelection(new Rectangle(2, 0, 1, Integer.MAX_VALUE)); + + assertTrue(isCellSelected(2, 0)); + assertTrue(isCellSelected(2, 1)); + assertFalse(isCellSelected(2, 2)); + } + + @Test + public void Clear_Selection() { + testee.addSelection(0, 0); + testee.addSelection(2, 1); + testee.clearSelection(); + + assertFalse(isCellSelected(0, 0)); + assertFalse(isCellSelected(2, 1)); + } + + @Test + public void Clear_Infinity_Long_Row_Only_Clear_Known_Columns() { + long expectedLoops = 3; + testee.addSelection(0, 0); + testee.addSelection(1, 0); + rowIdAccessor.numberOfCalls = 0; + testee.clearSelection(new Rectangle(0, 0, Integer.MAX_VALUE, 1)); + + assertEquals(expectedLoops, rowIdAccessor.numberOfCalls); + assertFalse(isCellSelected(0, 0)); + assertFalse(isCellSelected(1, 0)); + } + + @Test + public void Clear_Infinity_Long_Column_Only_Clear_Known_Rows() { + long expectedLoops = 3; + testee.addSelection(0, 0); + testee.addSelection(0, 1); + rowIdAccessor.numberOfCalls = 0; + testee.clearSelection(new Rectangle(0, 0, 1, Integer.MAX_VALUE)); + + assertEquals(expectedLoops, rowIdAccessor.numberOfCalls); + assertFalse(isCellSelected(0, 0)); + assertFalse(isCellSelected(0, 1)); + } + + @Test + public void Partial_Clear_Selection() { + testee.addSelection(0, 0); + testee.addSelection(2, 1); + testee.clearSelection(0, 0); + + assertFalse(isCellSelected(0, 0)); + assertTrue(isCellSelected(2, 1)); + } + + @Test + public void Clear_Selection_By_Rectangle() { + testee.addSelection(new Rectangle(1, 0, 2, 2)); + testee.clearSelection(new Rectangle(1, 0, 1, 2)); + + assertFalse(isCellSelected(1, 0)); + assertFalse(isCellSelected(1, 1)); + assertTrue(isCellSelected(2, 0)); + assertTrue(isCellSelected(2, 1)); + + testee.clearSelection(new Rectangle(2, 1, 1, 1)); + + assertTrue(isCellSelected(2, 0)); + assertFalse(isCellSelected(2, 1)); + } + + @Test + public void None_Selected_Cells_Is_Empty() { + assertTrue(testee.isEmpty()); + } + + @Test + public void Selected_Cell_Is_Not_Empty() { + testee.addSelection(0, 0); + + assertFalse(testee.isEmpty()); + } + + @Test + public void getSelectedColumnPositions() { + testee.addSelection(1, 1); + testee.addSelection(0, 0); + testee.addSelection(2, 2); + + int[] selectedColumns = testee.getSelectedColumnPositions(); + assertEquals(Arrays.toString(new int[] { 0, 1, 2 }), Arrays.toString(selectedColumns)); + } + + @Test + public void isColumnPositionSelected() { + int column = 0; + testee.addSelection(column, 0); + + assertTrue(testee.isColumnPositionSelected(column)); + assertFalse(testee.isColumnPositionSelected(2)); + } + + @Test + public void Changed_Sort_Order_Is_Properly_Reflected() { + /* + * row 0: A _ _ + * + * row 1: _ D _ + * + * row 2: _ _ F + */ + + // A + testee.addSelection(0, 0); + + // D + testee.addSelection(1, 1); + + // F + testee.addSelection(2, 2); + + assertTrue(isCellSelected(0, 0)); + assertTrue(isCellSelected(1, 1)); + assertTrue(isCellSelected(2, 2)); + + /* + * sort + * + * row 0: _ _ F + * + * row 1: _ D _ + * + * row 2: A _ _ + */ + // let index row 4 and index row 6 change places: + TestCell[] oldRow4 = cells[4]; + TestCell[] oldRow6 = cells[6]; + cells[4] = oldRow6; + cells[6] = oldRow4; + TestCell cellF = cells[4][2]; + TestCell cellA = cells[6][0]; + cellF.rowPosition = 0; // Position if index 4 + cellA.rowPosition = 2; // Position if index 6 + + rowDataProvider.indexOfRow4 = 6; + rowDataProvider.indexOfRow6 = 4; + + assertTrue(isCellSelected(2, 0)); + assertTrue(isCellSelected(1, 1)); + assertTrue(isCellSelected(0, 2)); + } + + @Test + public void isColumnFullySelected() throws Exception { + testee.addSelection(new Rectangle(1, 0, 2, 2)); + + assertFalse(testee.isColumnPositionFullySelected(1, 3)); + assertTrue(testee.isColumnPositionFullySelected(1, 2)); + } + + @Test + public void isColumnFullySelected_Copes_With_Clear() throws Exception { + testee.addSelection(new Rectangle(1, 0, 2, 2)); + testee.clearSelection(1, 0); + + assertFalse(testee.isColumnPositionFullySelected(1, 2)); + assertTrue(testee.isColumnPositionFullySelected(1, 1)); + } + + @Test + public void isColumnFullySelected_Copes_With_Gap() throws Exception { + testee.addSelection(2, 0); + testee.addSelection(2, 2); + + assertFalse(testee.isColumnPositionFullySelected(2, 2)); + } + + @Test + public void isColumnFullySelected_Copes_With_Overlapping_Regions() throws Exception { + testee.addSelection(new Rectangle(1, 0, 2, 2)); + testee.addSelection(new Rectangle(2, 1, 1, 2)); + + assertTrue(testee.isColumnPositionFullySelected(2, 3)); + } + + @Test + public void isColumnFullySelected_Copes_With_Combining_Adjacent_Individually_Selected_Cells() throws Exception { + testee.addSelection(1, 0); + testee.addSelection(1, 1); + + assertTrue(testee.isColumnPositionFullySelected(1, 2)); + } + + @Test + public void isColumnPositionFullySelected_For_Unselected_Column() { + assertFalse(testee.isColumnPositionFullySelected(1, 2)); + } + + @Test + public void getFullySelectedColumnPositions() { + testee.addSelection(0, 0); + testee.addSelection(new Rectangle(1, 0, 2, 2)); + testee.addSelection(new Rectangle(2, 1, 1, 2)); + + int[] fullySelectedColumns = testee.getFullySelectedColumnPositions(2); + assertEquals(Arrays.toString(new int[] { 1, 2 }), Arrays.toString(fullySelectedColumns)); + + fullySelectedColumns = testee.getFullySelectedColumnPositions(1); + assertEquals(Arrays.toString(new int[] { 0, 1, 2 }), Arrays.toString(fullySelectedColumns)); + + fullySelectedColumns = testee.getFullySelectedColumnPositions(3); + assertEquals(Arrays.toString(new int[] { 2 }), Arrays.toString(fullySelectedColumns)); + } + + @Test + public void getSelectedRowCount() { + testee.addSelection(1, 1); + testee.addSelection(0, 0); + testee.addSelection(2, 2); + + assertEquals(3, testee.getSelectedRowCount()); + } + + @Test + public void getSelectedRowPositions() { + testee.addSelection(1, 1); + testee.addSelection(0, 0); + testee.addSelection(2, 2); + + HashSet<Range> actualSelectedRowPositions = new HashSet<Range>(testee.getSelectedRowPositions()); + + HashSet<Range> expectedSelectedRowPositions = new HashSet<Range>(); + expectedSelectedRowPositions.add(new Range(0, 1)); + expectedSelectedRowPositions.add(new Range(1, 2)); + expectedSelectedRowPositions.add(new Range(2, 3)); + + assertEquals(expectedSelectedRowPositions, actualSelectedRowPositions); + } + + @Test + public void isRowPositionSelected() { + int row = 0; + testee.addSelection(0, row); + + assertTrue(testee.isRowPositionSelected(row)); + assertFalse(testee.isRowPositionSelected(2)); + } + + @Test + public void isRowPositionFullySelected() throws Exception { + testee.addSelection(new Rectangle(1, 0, 2, 2)); + + assertFalse(testee.isRowPositionFullySelected(0, 3)); + assertTrue(testee.isRowPositionFullySelected(0, 2)); + } + + @Test + public void isRowPositionFullySelected_Copes_With_Clear() throws Exception { + testee.addSelection(new Rectangle(1, 0, 2, 2)); + testee.clearSelection(1, 0); + + assertFalse(testee.isRowPositionFullySelected(0, 2)); + assertTrue(testee.isRowPositionFullySelected(0, 1)); + } + + @Test + public void isRowPositionFullySelected_Copes_With_Gap() throws Exception { + testee.addSelection(2, 0); + testee.addSelection(0, 0); + + assertFalse(testee.isRowPositionFullySelected(0, 2)); + } + + @Test + public void isRowPositionFullySelected_Copes_With_Overlapping_Regions() throws Exception { + testee.addSelection(new Rectangle(1, 0, 2, 2)); + testee.addSelection(new Rectangle(0, 0, 3, 1)); + + assertTrue(testee.isRowPositionFullySelected(0, 3)); + } + + @Test + public void isRowPositionFullySelected_Copes_With_Combining_Adjacent_Individually_Selected_Cells() throws Exception { + testee.addSelection(1, 0); + testee.addSelection(2, 0); + + assertTrue(testee.isRowPositionFullySelected(0, 2)); + } + + @Test + public void isRowPositionFullySelected_For_Unselected_Row() { + assertFalse(testee.isRowPositionFullySelected(0, 3)); + } + + @Test + public void getFullySelectedRowPositions() { + testee.addSelection(0, 0); + testee.addSelection(new Rectangle(1, 0, 2, 2)); + testee.addSelection(new Rectangle(2, 1, 1, 2)); + + int[] fullySelectedRows = testee.getFullySelectedRowPositions(2); + assertEquals(Arrays.toString(new int[] { 0, 1 }), Arrays.toString(fullySelectedRows)); + + fullySelectedRows = testee.getFullySelectedRowPositions(1); + assertEquals(Arrays.toString(new int[] { 0, 1, 2 }), Arrays.toString(fullySelectedRows)); + + fullySelectedRows = testee.getFullySelectedRowPositions(3); + assertEquals(Arrays.toString(new int[] { 0 }), Arrays.toString(fullySelectedRows)); + } + + // Anchor + + @Test + public void getSelectionAnchor_Copes_With_Missing_Marker() throws Exception { + assertEquals(SelectionLayer.NO_SELECTION, testee.getSelectionAnchor().x); + assertEquals(SelectionLayer.NO_SELECTION, testee.getSelectionAnchor().y); + } + + @Test + public void getSelectionAnchor() throws Exception { + testee.selectionAnchor = new CellPosition<String[]>(indexRow4, 0); + assertEquals(0, testee.getSelectionAnchor().x); + assertEquals(0, testee.getSelectionAnchor().y); + } + + @Test + public void setSelectionAnchor() throws Exception { + testee.setSelectionAnchor(new Point(0, 0)); + assertSame(indexRow4, testee.selectionAnchor.getRowObject()); + } + + // Last selected cell + + @Test + public void getLastSelectedCell_Copes_With_Missing_Marker() throws Exception { + assertEquals(SelectionLayer.NO_SELECTION, testee.getLastSelectedCell().x); + assertEquals(SelectionLayer.NO_SELECTION, testee.getLastSelectedCell().y); + } + + @Test + public void getLastSelectedCell() throws Exception { + testee.lastSelectedCell = new CellPosition<String[]>(indexRow4, 0); + assertEquals(0, testee.getLastSelectedCell().x); + assertEquals(0, testee.getLastSelectedCell().y); + } + + @Test + public void setLastSelectedCell() throws Exception { + testee.setLastSelectedCell(new Point(0, 0)); + assertSame(indexRow4, testee.lastSelectedCell.getRowObject()); + } + + // Last selected region + + @Test + public void getLastSelectedRegion_Copes_With_Missing_Marker() throws Exception { + assertNull(testee.getLastSelectedRegion()); + } + + @Test + public void getLastSelectedRegion() throws Exception { + testee.lastSelectedRegion = new Rectangle(0, 0, 1, 1); + testee.lastSelectedRegionOriginRowObject = indexRow6; + assertEquals(2, testee.getLastSelectedRegion().y); + } + + @Test + public void setLastSelectedRegion_Overrides_Reference() throws Exception { + Rectangle oldReference = new Rectangle(0, 0, 1, 1); + testee.lastSelectedRegion = oldReference; + Rectangle newReference = new Rectangle(0, 0, 1, 1); + testee.setLastSelectedRegion(newReference); + + assertSame(newReference, testee.lastSelectedRegion); + assertSame(indexRow4, testee.lastSelectedRegionOriginRowObject); + } + + @Test + public void setLastSelectedRegion_Clears_Region_On_NULL() throws Exception { + testee.lastSelectedRegion = new Rectangle(0, 0, 1, 1); + testee.setLastSelectedRegion(null); + + assertNull(testee.lastSelectedRegion); + } + + @Test + public void setLastSelectedRegion_On_Parameters_Copys_Data() throws Exception { + Rectangle oldReference = new Rectangle(0, 0, 1, 1); + testee.lastSelectedRegion = oldReference; + testee.setLastSelectedRegion(1, 1, 2, 2); + + assertSame(oldReference, testee.lastSelectedRegion); + assertEquals(new Rectangle(1, 1, 2, 2), testee.lastSelectedRegion); + assertSame(indexRow5, testee.lastSelectedRegionOriginRowObject); + } + + class TestRowDataProvider implements IRowDataProvider<String[]> { + + int indexOfRow4 = 4, indexOfRow5 = 5, indexOfRow6 = 6; + + @Override + public Object getDataValue(int columnIndex, int rowIndex) { + throw new NotImplementedException(); + } + + @Override + public void setDataValue(int columnIndex, int rowIndex, Object newValue) { + throw new NotImplementedException(); + } + + @Override + public int getColumnCount() { + return columnCount; + } + + @Override + public int getRowCount() { + return rowCount; + } + + @Override + public String[] getRowObject(int rowIndex) { + + if (rowIndex == indexOfRow4) { + return indexRow4; + } else if (rowIndex == indexOfRow5) { + return indexRow5; + } else if (rowIndex == indexOfRow6) { + return indexRow6; + } else { + throw new NotImplementedException(); + } + } + + @Override + public int indexOfRowObject(String[] rowObject) { + if (rowObject == indexRow4) { + return indexOfRow4; + } else if (rowObject == indexRow5) { + return indexOfRow5; + } else if (rowObject == indexRow6) { + return indexOfRow6; + } else { + throw new NotImplementedException(); + } + } + } + + class TestRowIdAccessor implements IRowIdAccessor<String[]> { + + long numberOfCalls = 0; + + @Override + public Serializable getRowId(String[] rowObject) { + numberOfCalls += 1; + if (rowObject == indexRow4) { + return "rowA"; + } else if (rowObject == indexRow5) { + + return "rowB"; + } else if (rowObject == indexRow6) { + + return "rowC"; + } else { + throw new NotImplementedException(); + } + } + } + + class TestSelectionLayer extends SelectionLayer { + + int scrollOffset = 4; + + public TestSelectionLayer(IUniqueIndexLayer underlyingLayer) { + super(underlyingLayer); + } + + @Override + public int getRowIndexByPosition(int rowPosition) { + int rowIndex = rowPosition + scrollOffset; + return rowIndex < 0 || rowIndex > 6 ? -1 : rowIndex; + } + + @Override + public int getRowPositionByIndex(int rowIndex) { + int rowPosition = Math.max(rowIndex - scrollOffset, -1); + return rowPosition > 2 ? -1 : rowPosition; + }; + + @Override + public ILayerCell getCellByPosition(int columnPosition, int rowPosition) { + return cells[getRowIndexByPosition(rowPosition)][columnPosition]; + } + } + + class TestCell extends LayerCell { + + int columnPosition, rowPosition; + + public TestCell(int columnPosition, int rowPosition) { + super(null, columnPosition, rowPosition); + this.columnPosition = columnPosition; + this.rowPosition = rowPosition; + } + + public TestCell() { + super(null, 0, 0, 0, 0, 0, 0); + } + + @Override + public int getOriginRowPosition() { + return rowPosition; + } + + @Override + public int getOriginColumnPosition() { + return columnPosition; + } + + @Override + public int getRowSpan() { + return super.getRowSpan(); + } + + @Override + public int getColumnSpan() { + return super.getColumnSpan(); + } + + } + +} diff --git a/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/preserve/SelectionsTest.java b/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/preserve/SelectionsTest.java new file mode 100644 index 00000000..8793bc93 --- /dev/null +++ b/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/selection/preserve/SelectionsTest.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * 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 test + ******************************************************************************/ +package org.eclipse.nebula.widgets.nattable.selection.preserve; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.Serializable; +import java.util.HashSet; + +import org.eclipse.nebula.widgets.nattable.selection.preserve.Selections; +import org.eclipse.nebula.widgets.nattable.selection.preserve.Selections.CellPosition; +import org.junit.Test; + +public class SelectionsTest { + + private Selections<String[]> testee = new Selections<String[]>(); + + private Serializable rowA = "rowA"; + + private Serializable rowB = "rowB"; + + /** + * Each row consist of an array of String, one String for each column, i e + * each cell is a String. + */ + private String[] rowObjectA = new String[] { "good", "day" }; + + private String[] rowObjectB = new String[] { "bad", "night" }; + + private int columnPosition1 = 1; + + private int columnPosition2 = 2; + + private int columnPosition3 = 3; + + @Test + public void Never_Selected_Cell_Is_Not_Selected() { + assertFalse(testee.isSelected(rowA, columnPosition2)); + } + + @Test + public void Selecting_A_Cell_For_Unselected_Row() { + testee.select(rowA, rowObjectA, columnPosition2); + assertTrue(testee.isSelected(rowA, columnPosition2)); + } + + @Test + public void Selecting_A_Cell_For_Already_Selected_Row() { + testee.select(rowA, rowObjectA, columnPosition1); + testee.select(rowA, rowObjectA, columnPosition2); + assertTrue(testee.isSelected(rowA, columnPosition1)); + assertTrue(testee.isSelected(rowA, columnPosition2)); + } + + @Test + public void Clear_Removes_All_Selections() { + testee.select(rowA, rowObjectA, columnPosition1); + testee.select(rowA, rowObjectA, columnPosition2); + testee.select(rowB, rowObjectB, columnPosition2); + testee.clear(); + assertFalse(testee.isSelected(rowA, columnPosition1)); + assertFalse(testee.isSelected(rowA, columnPosition2)); + assertFalse(testee.isSelected(rowB, columnPosition2)); + + assertTrue(testee.getRows().isEmpty()); + assertTrue(testee.getColumnPositions().isEmpty()); + } + + @Test + public void Deselecting_Cells_Does_Only_Affect_Those_Cells() { + // cell not to be touched + testee.select(rowA, rowObjectA, columnPosition1); + + // Cells to be touched + testee.select(rowA, rowObjectA, columnPosition2); + testee.select(rowB, rowObjectB, columnPosition2); + testee.deselect(rowA, columnPosition2); + testee.deselect(rowB, columnPosition2); + + assertTrue(testee.isSelected(rowA, columnPosition1)); + assertFalse(testee.isSelected(rowA, columnPosition2)); + assertFalse(testee.isSelected(rowB, columnPosition2)); + } + + @Test + public void Fully_Deselected_Row_Doesent_Linger() { + testee.select(rowA, rowObjectA, columnPosition1); + testee.select(rowA, rowObjectA, columnPosition2); + testee.deselect(rowA, columnPosition1); + testee.deselect(rowA, columnPosition2); + + assertFalse(testee.isRowSelected(rowA)); + } + + @Test + public void None_Selected_Cells_Is_Empty() { + assertTrue(testee.isEmpty()); + } + + @Test + public void Selected_Cell_Is_Not_Empty() { + testee.select(rowA, rowObjectA, columnPosition1); + assertFalse(testee.isEmpty()); + } + + @Test + public void Fully_Deselecting_All_Rows_Causes_Is_Empty() { + testee.select(rowA, rowObjectA, columnPosition1); + testee.deselect(rowA, columnPosition1); + assertTrue(testee.isEmpty()); + } + + @Test + public void getSelections_Retrieves_All_Cells() { + testee.select(rowA, rowObjectA, columnPosition2); + testee.select(rowB, rowObjectB, columnPosition2); + + HashSet<CellPosition<String[]>> actualCells = new HashSet<CellPosition<String[]>>(testee.getSelections()); + + HashSet<CellPosition<String[]>> expectedCells = new HashSet<CellPosition<String[]>>(); + expectedCells.add(new CellPosition<String[]>(rowObjectA, columnPosition2)); + expectedCells.add(new CellPosition<String[]>(rowObjectB, columnPosition2)); + + assertEquals(expectedCells, actualCells); + } + + @Test + public void getRows() { + testee.select(rowA, rowObjectA, columnPosition2); + testee.select(rowB, rowObjectB, columnPosition2); + + HashSet<Serializable> actualRowIds = new HashSet<Serializable>(); + for (Selections<String[]>.Row row : testee.getRows()) { + actualRowIds.add(row.getId()); + } + + HashSet<Serializable> expectedRowIds = new HashSet<Serializable>(); + expectedRowIds.add(rowA); + expectedRowIds.add(rowB); + + assertEquals(expectedRowIds, actualRowIds); + } + + @Test + public void getColumns() { + testee.select(rowA, rowObjectA, columnPosition2); + testee.select(rowA, rowObjectA, columnPosition3); + testee.select(rowB, rowObjectB, columnPosition1); + + HashSet<Integer> actualColumns = new HashSet<Integer>(testee.getColumnPositions()); + + HashSet<Integer> expectedColumns = new HashSet<Integer>(); + expectedColumns.add(columnPosition2); + expectedColumns.add(columnPosition1); + expectedColumns.add(columnPosition3); + + assertEquals(expectedColumns, actualColumns); + } + +} diff --git a/org.eclipse.nebula.widgets.nattable.core/META-INF/MANIFEST.MF b/org.eclipse.nebula.widgets.nattable.core/META-INF/MANIFEST.MF index ec22dce8..50890cfa 100644 --- a/org.eclipse.nebula.widgets.nattable.core/META-INF/MANIFEST.MF +++ b/org.eclipse.nebula.widgets.nattable.core/META-INF/MANIFEST.MF @@ -110,6 +110,7 @@ Export-Package: org.eclipse.nebula.widgets.nattable, org.eclipse.nebula.widgets.nattable.selection.command, org.eclipse.nebula.widgets.nattable.selection.config, org.eclipse.nebula.widgets.nattable.selection.event, + org.eclipse.nebula.widgets.nattable.selection.preserve, org.eclipse.nebula.widgets.nattable.serializing, org.eclipse.nebula.widgets.nattable.sort, org.eclipse.nebula.widgets.nattable.sort.action, diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/IMarkerSelectionModel.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/IMarkerSelectionModel.java new file mode 100644 index 00000000..32d1b8ba --- /dev/null +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/IMarkerSelectionModel.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +package org.eclipse.nebula.widgets.nattable.selection; + +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; + +/** + * Selection model that holds markers such as anchors and last selection properties in order to + * keep them up-to-date after underlying data has changed. + */ +public interface IMarkerSelectionModel extends ISelectionModel { + + /** + * @return point of the anchor expressed in position coordinates + */ + Point getSelectionAnchor(); + + /** + * @return point of the last selected cell expressed in position coordinates + */ + Point getLastSelectedCell(); + + /** + * @return rectangle of the last selected region expressed in position coordinates + */ + Rectangle getLastSelectedRegion(); + + /** + * @param position + * coordinate of the anchor + */ + void setSelectionAnchor(Point position); + + /** + * @param position + * coordinate of the last selected + */ + void setLastSelectedCell(Point position); + + /** + * Will set the Rectangle object of the last selected region to be the same as the parameter + * object region. + * + * @param region + * the last selection position region + */ + void setLastSelectedRegion(Rectangle region); + + /** + * Will copy the information of the parameters to the already existing Rectangle object of + * last selected region. + * + * @param x + * origin of the last selection position region + * @param y + * origin of the last selection position region + * @param width + * of the last selection position region + * @param height + * of the last selection position region + */ + void setLastSelectedRegion(int x, int y, int width, int height); +}
\ No newline at end of file diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/ISelectionModel.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/ISelectionModel.java index 090735e2..aece586d 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/ISelectionModel.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/ISelectionModel.java @@ -22,32 +22,84 @@ import org.eclipse.swt.graphics.Rectangle; */ public interface ISelectionModel { + /** + * Determines whether multiple cells can be selected simultaneously + * @return whether multiple cells can be selected simultaneously + */ public boolean isMultipleSelectionAllowed(); + /** + * Sets whether multiple cells can be selected simultaneously + * @param multipleSelectionAllowed whether multiple cells can be selected simultaneously + */ public void setMultipleSelectionAllowed(boolean multipleSelectionAllowed); + /** + * Selects a specified cell + * @param columnPosition column position of the cell to select + * @param rowPosition row position of the cell to select + */ public void addSelection(int columnPosition, int rowPosition); + /** + * Selects the cells of a specified area + * @param range the position based area to select + */ public void addSelection(final Rectangle range); + /** + * Removes all cell selections + */ public void clearSelection(); + /** + * Deselects a specified cell + * @param columnPosition column position of the cell to deselect + * @param rowPosition row position of the cell to deselect + */ public void clearSelection(int columnPosition, int rowPosition); + /** + * Removes the selection of specified cells + * @param removedSelection the position based area to deselect + */ public void clearSelection(Rectangle removedSelection); + /** + * Determines whether there are any selected cells + * @return whether there are any selected cells + */ public boolean isEmpty(); + /** + * Retrieves the cells that are selected + * @return the cells that are selected, expressed in position coordinates + */ public List<Rectangle> getSelections(); // Cell features + /** + * Determines whether a specified cell is selected + * @param columnPosition column position of the cell to inspect + * @param rowPosition row position of the cell to inspect + * @return whether the specified cell is selected + */ public boolean isCellPositionSelected(int columnPosition, int rowPosition); // Column features + /** + * Retrieves the columns that have any selected cells + * @return the column positions that have any selected cells + */ public int[] getSelectedColumnPositions(); + /** + * Determines whether a specified column contains any selected cell + * @param columnPosition column position to inspect + * @return whether the specified column contains any selected cell + */ public boolean isColumnPositionSelected(int columnPosition); /** @@ -62,10 +114,23 @@ public interface ISelectionModel { // Row features + /** + * Retrieves the number of rows that have any selected cell + * @return the number of rows that have any selected cell + */ public int getSelectedRowCount(); + /** + * Retrieves the rows with a valid row position that have any selected cells + * @return the row positions with a valid row position that have any selected cells + */ public Set<Range> getSelectedRowPositions(); + /** + * Determines whether a specified row contains any selected cell + * @param rowPosition row position to inspect + * @return whether the specified row contains any selected cell + */ public boolean isRowPositionSelected(int rowPosition); /** diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectCellCommandHandler.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectCellCommandHandler.java index 79da8830..72ba2233 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectCellCommandHandler.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectCellCommandHandler.java @@ -7,6 +7,9 @@ * * Contributors: * Original authors and others - initial API and implementation + * Jonas Hugo <Jonas.Hugo@jeppesen.com>, + * Markus Wahl <Markus.Wahl@jeppesen.com> - Use getters and setters for + * the markers of SelectionLayer instead of the fields. ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.selection; @@ -68,36 +71,37 @@ public class SelectCellCommandHandler implements ILayerCommandHandler<SelectCell selectionLayer.setLastSelectedCell(cell.getOriginColumnPosition(), cell.getOriginRowPosition()); // Shift pressed + row selected - if (selectionLayer.getSelectionModel().isMultipleSelectionAllowed() - && withShiftMask - && selectionLayer.lastSelectedRegion != null + if (selectionLayer.getSelectionModel().isMultipleSelectionAllowed() + && withShiftMask + && selectionLayer.getLastSelectedRegion() != null && selectionLayer.hasRowSelection() - && (selectionLayer.selectionAnchor.rowPosition != SelectionLayer.NO_SELECTION) - && (selectionLayer.selectionAnchor.columnPosition != SelectionLayer.NO_SELECTION)) { - // if cell.rowPosition > selectionAnchor.rowPositon, then use cell.rowPosition + span - 1 (maxRowPosition) + && (selectionLayer.getSelectionAnchor().rowPosition != SelectionLayer.NO_SELECTION) + && (selectionLayer.getSelectionAnchor().columnPosition != SelectionLayer.NO_SELECTION)) { + // if cell.rowPosition > getSelectionAnchor().rowPositon, then + // use cell.rowPosition + span - 1 (maxRowPosition) // else use cell.originRowPosition (minRowPosition) // and compare with selectionAnchor.rowPosition - if (cell.getRowPosition() > selectionLayer.selectionAnchor.rowPosition) { + if (cell.getRowPosition() > selectionLayer.getSelectionAnchor().rowPosition) { int maxRowPosition = cell.getOriginRowPosition() + cell.getRowSpan() - 1; - selectionLayer.lastSelectedRegion.height = Math.abs(selectionLayer.selectionAnchor.rowPosition - maxRowPosition) + 1; + selectionLayer.getLastSelectedRegion().height = Math.abs(selectionLayer.getSelectionAnchor().rowPosition - maxRowPosition) + 1; } else { int minRowPosition = cell.getOriginRowPosition(); - selectionLayer.lastSelectedRegion.height = Math.abs(selectionLayer.selectionAnchor.rowPosition - minRowPosition) + 1; + selectionLayer.getLastSelectedRegion().height = Math.abs(selectionLayer.getSelectionAnchor().rowPosition - minRowPosition) + 1; } - selectionLayer.lastSelectedRegion.y = Math.min(selectionLayer.selectionAnchor.rowPosition, cell.getOriginRowPosition()); - - if (cell.getColumnPosition() > selectionLayer.selectionAnchor.columnPosition) { + selectionLayer.getLastSelectedRegion().y = Math.min(selectionLayer.getSelectionAnchor().rowPosition, cell.getOriginRowPosition()); + + if (cell.getColumnPosition() > selectionLayer.getSelectionAnchor().columnPosition) { int maxColumnPosition = cell.getOriginColumnPosition() + cell.getColumnSpan() - 1; - selectionLayer.lastSelectedRegion.width = Math.abs(selectionLayer.selectionAnchor.columnPosition - maxColumnPosition) + 1; + selectionLayer.getLastSelectedRegion().width = Math.abs(selectionLayer.getSelectionAnchor().columnPosition - maxColumnPosition) + 1; } else { int minColumnPosition = cell.getOriginColumnPosition(); - selectionLayer.lastSelectedRegion.width = Math.abs(selectionLayer.selectionAnchor.columnPosition - minColumnPosition) + 1; + selectionLayer.getLastSelectedRegion().width = Math.abs(selectionLayer.getSelectionAnchor().columnPosition - minColumnPosition) + 1; } - selectionLayer.lastSelectedRegion.x = Math.min(selectionLayer.selectionAnchor.columnPosition, cell.getOriginColumnPosition()); - - selectionLayer.addSelection(selectionLayer.lastSelectedRegion); + selectionLayer.getLastSelectedRegion().x = Math.min(selectionLayer.getSelectionAnchor().columnPosition, cell.getOriginColumnPosition()); + + selectionLayer.addSelection(selectionLayer.getLastSelectedRegion()); } else { - selectionLayer.lastSelectedRegion = null; + selectionLayer.setLastSelectedRegion(null); Rectangle selection = new Rectangle(cell.getOriginColumnPosition(), cell.getOriginRowPosition(), cell.getColumnSpan(), cell.getRowSpan()); selectionLayer.addSelection(selection); diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectColumnCommandHandler.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectColumnCommandHandler.java index 7e3897fc..a029b960 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectColumnCommandHandler.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectColumnCommandHandler.java @@ -7,6 +7,9 @@ * * Contributors: * Original authors and others - initial API and implementation + * Jonas Hugo <Jonas.Hugo@jeppesen.com>, + * Markus Wahl <Markus.Wahl@jeppesen.com> - Use getters and setters for + * the markers of SelectionLayer instead of the fields. ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.selection; @@ -54,8 +57,7 @@ public class SelectColumnCommandHandler implements ILayerCommandHandler<SelectCo } // Set last selected column position to the recently clicked column - selectionLayer.lastSelectedCell.columnPosition = columnPosition; - selectionLayer.lastSelectedCell.rowPosition = rowPosition; + selectionLayer.setLastSelectedCell(columnPosition, rowPosition); selectionLayer.fireLayerEvent(new ColumnSelectionEvent(selectionLayer, columnPosition)); } @@ -65,16 +67,16 @@ public class SelectColumnCommandHandler implements ILayerCommandHandler<SelectCo if (selectionLayer.isColumnPositionFullySelected(columnPosition)) { selectionLayer.clearSelection(selectedColumnRectangle); - if(selectionLayer.lastSelectedRegion != null && selectionLayer.lastSelectedRegion.equals(selectedColumnRectangle)){ - selectionLayer.lastSelectedRegion = null; + if (selectionLayer.getLastSelectedRegion() != null && selectionLayer.getLastSelectedRegion().equals(selectedColumnRectangle)) { + selectionLayer.setLastSelectedRegion(null); } - }else{ - if(selectionLayer.lastSelectedRegion != null){ - selectionLayer.selectionModel.addSelection( - new Rectangle(selectionLayer.lastSelectedRegion.x, - selectionLayer.lastSelectedRegion.y, - selectionLayer.lastSelectedRegion.width, - selectionLayer.lastSelectedRegion.height)); + } else { + if (selectionLayer.getLastSelectedRegion() != null) { + selectionLayer.selectionModel.addSelection(new Rectangle( + selectionLayer.getLastSelectedRegion().x, + selectionLayer.getLastSelectedRegion().y, + selectionLayer.getLastSelectedRegion().width, + selectionLayer.getLastSelectedRegion().height)); } selectionLayer.selectRegion(columnPosition, 0, 1, Integer.MAX_VALUE); selectionLayer.moveSelectionAnchor(columnPosition, rowPosition); @@ -88,24 +90,25 @@ public class SelectColumnCommandHandler implements ILayerCommandHandler<SelectCo //if multiple selection is disabled, we need to ensure to only select the current columnPosition //modifying the selection anchor here ensures that the anchor also moves if (!selectionLayer.getSelectionModel().isMultipleSelectionAllowed()) { - selectionLayer.selectionAnchor.columnPosition = columnPosition; + selectionLayer.getSelectionAnchor().columnPosition = columnPosition; } - - if (selectionLayer.lastSelectedRegion != null) { - - // Negative when we move left, but we are only concerned with the num. of columns - numOfColumnsToIncludeInRegion = Math.abs(selectionLayer.selectionAnchor.columnPosition - columnPosition) + 1; - + + if (selectionLayer.getLastSelectedRegion() != null) { + + // Negative when we move left, but we are only concerned with the + // num. of columns + numOfColumnsToIncludeInRegion = Math.abs(selectionLayer.getSelectionAnchor().columnPosition - columnPosition) + 1; + // Select to the Left - if (columnPosition < selectionLayer.selectionAnchor.columnPosition) { + if (columnPosition < selectionLayer.getSelectionAnchor().columnPosition) { startColumnPosition = columnPosition; } else { - startColumnPosition = selectionLayer.selectionAnchor.columnPosition; + startColumnPosition = selectionLayer.getSelectionAnchor().columnPosition; } } selectionLayer.selectRegion(startColumnPosition, 0, numOfColumnsToIncludeInRegion, Integer.MAX_VALUE); } - + @Override public Class<SelectColumnCommand> getCommandClass() { return SelectColumnCommand.class; diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectRowCommandHandler.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectRowCommandHandler.java index 647b63b9..706b8d79 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectRowCommandHandler.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectRowCommandHandler.java @@ -7,6 +7,9 @@ * * Contributors: * Original authors and others - initial API and implementation + * Jonas Hugo <Jonas.Hugo@jeppesen.com>, + * Markus Wahl <Markus.Wahl@jeppesen.com> - Use getters and setters for + * the markers of SelectionLayer instead of the fields. ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.selection; @@ -77,9 +80,8 @@ public class SelectRowCommandHandler implements ILayerCommandHandler<SelectRowsC changedRowRanges.add(selectRowWithCtrlKey(columnPosition, rowPosition)); } - selectionLayer.lastSelectedCell.columnPosition = columnPosition; - selectionLayer.lastSelectedCell.rowPosition = rowPosition; - + selectionLayer.setLastSelectedCell(columnPosition, rowPosition); + return changedRowRanges; } @@ -88,17 +90,17 @@ public class SelectRowCommandHandler implements ILayerCommandHandler<SelectRowsC if (selectionLayer.isRowPositionFullySelected(rowPosition)) { selectionLayer.clearSelection(selectedRowRectangle); - if (selectionLayer.lastSelectedRegion != null && selectionLayer.lastSelectedRegion.equals(selectedRowRectangle)) { - selectionLayer.lastSelectedRegion = null; + if (selectionLayer.getLastSelectedRegion() != null && selectionLayer.getLastSelectedRegion().equals(selectedRowRectangle)) { + selectionLayer.setLastSelectedRegion(null); } } else { // Preserve last selected region - if (selectionLayer.lastSelectedRegion != null) { - selectionLayer.selectionModel.addSelection( - new Rectangle(selectionLayer.lastSelectedRegion.x, - selectionLayer.lastSelectedRegion.y, - selectionLayer.lastSelectedRegion.width, - selectionLayer.lastSelectedRegion.height)); + if (selectionLayer.getLastSelectedRegion() != null) { + selectionLayer.selectionModel.addSelection(new Rectangle( + selectionLayer.getLastSelectedRegion().x, + selectionLayer.getLastSelectedRegion().y, + selectionLayer.getLastSelectedRegion().width, + selectionLayer.getLastSelectedRegion().height)); } selectionLayer.selectRegion(0, rowPosition, Integer.MAX_VALUE, 1); selectionLayer.moveSelectionAnchor(columnPosition, rowPosition); @@ -114,17 +116,17 @@ public class SelectRowCommandHandler implements ILayerCommandHandler<SelectRowsC //if multiple selection is disabled, we need to ensure to only select the current rowPosition //modifying the selection anchor here ensures that the anchor also moves if (!selectionLayer.getSelectionModel().isMultipleSelectionAllowed()) { - selectionLayer.selectionAnchor.rowPosition = rowPosition; + selectionLayer.getSelectionAnchor().rowPosition = rowPosition; } - - if (selectionLayer.lastSelectedRegion != null) { - numOfRowsToIncludeInRegion = Math.abs(selectionLayer.selectionAnchor.rowPosition - rowPosition) + 1; - if (startRowPosition < selectionLayer.selectionAnchor.rowPosition) { + + if (selectionLayer.getLastSelectedRegion() != null) { + numOfRowsToIncludeInRegion = Math.abs(selectionLayer.getSelectionAnchor().rowPosition - rowPosition) + 1; + if (startRowPosition < selectionLayer.getSelectionAnchor().rowPosition) { // Selecting above startRowPosition = rowPosition; } else { // Selecting below - startRowPosition = selectionLayer.selectionAnchor.rowPosition; + startRowPosition = selectionLayer.getSelectionAnchor().rowPosition; } } selectionLayer.selectRegion(0, startRowPosition, Integer.MAX_VALUE, numOfRowsToIncludeInRegion); diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectRowGroupCommandHandler.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectRowGroupCommandHandler.java index 9411cb3e..47007a9d 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectRowGroupCommandHandler.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectRowGroupCommandHandler.java @@ -7,6 +7,9 @@ * * Contributors: * Original authors and others - initial API and implementation + * Jonas Hugo <Jonas.Hugo@jeppesen.com>, + * Markus Wahl <Markus.Wahl@jeppesen.com> - Use getters and setters for + * the markers of SelectionLayer instead of the fields. ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.selection; @@ -41,7 +44,7 @@ public class SelectRowGroupCommandHandler<T> extends AbstractLayerCommandHandler this.selectionLayer = selectionLayer; this.rowGroupHeaderLayer = rowGroupHeaderLayer; } - + @Override public Class<SelectRowGroupsCommand> getCommandClass() { return SelectRowGroupsCommand.class; @@ -49,16 +52,16 @@ public class SelectRowGroupCommandHandler<T> extends AbstractLayerCommandHandler @Override protected boolean doCommand(SelectRowGroupsCommand command) { - final List<Integer> rowIndexes = RowGroupUtils.getRowIndexesInGroup(model, rowGroupHeaderLayer.getRowIndexByPosition( command.getRowPosition() ) ); - final List<Integer> rowPositions = RowGroupUtils.getRowPositionsInGroup( selectionLayer, rowIndexes ); + final List<Integer> rowIndexes = RowGroupUtils.getRowIndexesInGroup(model, rowGroupHeaderLayer.getRowIndexByPosition(command.getRowPosition())); + final List<Integer> rowPositions = RowGroupUtils.getRowPositionsInGroup(selectionLayer, rowIndexes); selectRows(command.getColumnPosition(), rowPositions, command.isWithShiftMask(), command.isWithControlMask(), command.getRowPositionToMoveIntoViewport(), command.isMoveAnchorToTopOfGroup()); return true; } - + protected void selectRows(int columnPosition, List<Integer> rowPositions, boolean withShiftMask, boolean withControlMask, int rowPositionToMoveIntoViewport, boolean moveAnchorToTopOfGroup) { Set<Range> changedRowRanges = new HashSet<Range>(); - - if( rowPositions.size() > 0 ) { + + if (rowPositions.size() > 0) { changedRowRanges.addAll(internalSelectRow(columnPosition, rowPositions.get(0), rowPositions.size(), withShiftMask, withControlMask, moveAnchorToTopOfGroup)); } @@ -70,10 +73,10 @@ public class SelectRowGroupCommandHandler<T> extends AbstractLayerCommandHandler } selectionLayer.fireLayerEvent(new RowSelectionEvent(selectionLayer, changedRows, rowPositionToMoveIntoViewport)); } - + private Set<Range> internalSelectRow(int columnPosition, int rowPosition, int rowCount, boolean withShiftMask, boolean withControlMask, boolean moveAnchorToTopOfGroup) { Set<Range> changedRowRanges = new HashSet<Range>(); - + if (noShiftOrControl(withShiftMask, withControlMask)) { changedRowRanges.addAll(selectionLayer.getSelectedRowPositions()); selectionLayer.clear(false); @@ -87,55 +90,53 @@ public class SelectRowGroupCommandHandler<T> extends AbstractLayerCommandHandler } if (moveAnchorToTopOfGroup) { selectionLayer.moveSelectionAnchor(columnPosition, rowPosition); - } - selectionLayer.lastSelectedCell.columnPosition = selectionLayer.getColumnCount() - 1; - selectionLayer.lastSelectedCell.rowPosition = rowPosition; - + } + selectionLayer.getLastSelectedCellPosition().columnPosition = selectionLayer.getColumnCount() - 1; + selectionLayer.getLastSelectedCellPosition().rowPosition = rowPosition; + return changedRowRanges; } - + private Range selectRowWithCtrlKey(int columnPosition, int rowPosition, int rowCount) { Rectangle selectedRowRectangle = new Rectangle(0, rowPosition, selectionLayer.getColumnCount(), rowCount); if (selectionLayer.isRowPositionFullySelected(rowPosition)) { selectionLayer.clearSelection(selectedRowRectangle); - if (selectionLayer.lastSelectedRegion != null && selectionLayer.lastSelectedRegion.equals(selectedRowRectangle)) { - selectionLayer.lastSelectedRegion = null; + if (selectionLayer.getLastSelectedRegion() != null && selectionLayer.getLastSelectedRegion().equals(selectedRowRectangle)) { + selectionLayer.setLastSelectedRegion(null); } } else { // Preserve last selected region - if (selectionLayer.lastSelectedRegion != null) { - selectionLayer.selectionModel.addSelection( - new Rectangle(selectionLayer.lastSelectedRegion.x, - selectionLayer.lastSelectedRegion.y, - selectionLayer.lastSelectedRegion.width, - selectionLayer.lastSelectedRegion.height)); + if (selectionLayer.getLastSelectedRegion() != null) { + selectionLayer.selectionModel.addSelection(new Rectangle( + selectionLayer.getLastSelectedRegion().x, + selectionLayer.getLastSelectedRegion().y, + selectionLayer.getLastSelectedRegion().width, + selectionLayer.getLastSelectedRegion().height)); } selectionLayer.selectRegion(0, rowPosition, selectionLayer.getColumnCount(), rowCount); } - + return new Range(rowPosition, rowPosition + 1); } private Range selectRowWithShiftKey(int columnPosition, int rowPosition, int rowCount) { - if (selectionLayer.lastSelectedRegion != null) { - int start = Math.min(selectionLayer.lastSelectedRegion.y, rowPosition); - int end = Math.max(selectionLayer.lastSelectedRegion.y, rowPosition); - - - for(int i = start; i <= end; i++){ - int index = selectionLayer.getRowIndexByPosition(i); - if(RowGroupUtils.isPartOfAGroup(model, index) && !selectionLayer.isRowPositionFullySelected(i)){ - List <Integer> rowPositions = new ArrayList<Integer>(RowGroupUtils.getRowPositionsInGroup(selectionLayer, RowGroupUtils.getRowIndexesInGroup(model, index))); - Collections.sort(rowPositions); - selectionLayer.selectRegion(0, rowPositions.get(0), selectionLayer.getColumnCount(), rowPositions.size()); - i=ObjectUtils.getLastElement(rowPositions); + if (selectionLayer.getLastSelectedRegion() != null) { + int start = Math.min(selectionLayer.getLastSelectedRegion().y, rowPosition); + int end = Math.max(selectionLayer.getLastSelectedRegion().y, rowPosition); + + for (int i = start; i <= end; i++) { + int index = selectionLayer.getRowIndexByPosition(i); + if (RowGroupUtils.isPartOfAGroup(model, index) && !selectionLayer.isRowPositionFullySelected(i)) { + List<Integer> rowPositions = new ArrayList<Integer>(RowGroupUtils.getRowPositionsInGroup(selectionLayer, RowGroupUtils.getRowIndexesInGroup(model, index))); + Collections.sort(rowPositions); + selectionLayer.selectRegion(0, rowPositions.get(0), selectionLayer.getColumnCount(), rowPositions.size()); + i = ObjectUtils.getLastElement(rowPositions); + } } - } - - + } return new Range(rowPosition, rowPosition + 1); } - + } diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectionLayer.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectionLayer.java index 5cbc890c..f1355199 100644 --- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectionLayer.java +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectionLayer.java @@ -7,6 +7,10 @@ * * Contributors: * Original authors and others - initial API and implementation + * Jonas Hugo <Jonas.Hugo@jeppesen.com>, + * Markus Wahl <Markus.Wahl@jeppesen.com> - Delegate markers to model iff + * model is an IMarkerSelectionModel. Add getters and setters for marker + * fields. ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.selection; @@ -126,11 +130,9 @@ public class SelectionLayer extends AbstractIndexLayerTransform { } public void addSelection(Rectangle selection) { - if (selection != lastSelectedRegion) { - selectionAnchor.columnPosition = lastSelectedCell.columnPosition; - selectionAnchor.rowPosition = lastSelectedCell.rowPosition; - - lastSelectedRegion = selection; + if (selection != getLastSelectedRegion()) { + setSelectionAnchor(getLastSelectedCell().columnPosition, getLastSelectedCell().rowPosition); + setLastSelectedRegion(selection); } selectionModel.addSelection(selection); @@ -142,17 +144,15 @@ public class SelectionLayer extends AbstractIndexLayerTransform { public void clear(boolean fireSelectionEvent) { selectionModel.clearSelection(); - - boolean validLastSelectedCell = lastSelectedCell.columnPosition != NO_SELECTION && lastSelectedCell.rowPosition != NO_SELECTION; - lastSelectedCell.columnPosition = -1; - lastSelectedCell.rowPosition = -1; - lastSelectedRegion = new Rectangle(0,0,0,0); - - selectionAnchor.columnPosition = -1; - selectionAnchor.rowPosition = -1; - + + boolean validLastSelectedCell = hasSelection(getLastSelectedCell()); + setLastSelectedCell(NO_SELECTION, NO_SELECTION); + setLastSelectedRegion(new Rectangle(0, 0, 0, 0)); + + setSelectionAnchor(NO_SELECTION, NO_SELECTION); + if (validLastSelectedCell && fireSelectionEvent) { - fireCellSelectionEvent(lastSelectedCell.columnPosition, lastSelectedCell.rowPosition, false, false, false); + fireCellSelectionEvent(getLastSelectedCell().columnPosition, getLastSelectedCell().rowPosition, false, false, false); } } @@ -162,24 +162,22 @@ public class SelectionLayer extends AbstractIndexLayerTransform { public void clearSelection(Rectangle selection) { selectionModel.clearSelection(selection); - - //if the selection anchor is within the selection that is removed - //it needs to be cleared also - Point anchorPoint = new Point(selectionAnchor.columnPosition, selectionAnchor.rowPosition); + + // if the selection anchor is within the selection that is removed + // it needs to be cleared also + Point anchorPoint = new Point(getSelectionAnchor().columnPosition, getSelectionAnchor().rowPosition); if (selection.contains(anchorPoint)) { - selectionAnchor.columnPosition = -1; - selectionAnchor.rowPosition = -1; + setSelectionAnchor(NO_SELECTION, NO_SELECTION); } } public void selectAll() { Rectangle selection = new Rectangle(0, 0, getColumnCount(), getRowCount()); - if(lastSelectedCell.columnPosition == SelectionLayer.NO_SELECTION || lastSelectedCell.rowPosition == SelectionLayer.NO_SELECTION){ - lastSelectedCell.rowPosition = 0; - lastSelectedCell.columnPosition = 0; + if (getLastSelectedCell().columnPosition == SelectionLayer.NO_SELECTION || getLastSelectedCell().rowPosition == SelectionLayer.NO_SELECTION) { + setLastSelectedCell(0, 0); } addSelection(selection); - fireCellSelectionEvent(lastSelectedCell.columnPosition, lastSelectedCell.rowPosition, false, false, false); + fireCellSelectionEvent(getLastSelectedCell().columnPosition, getLastSelectedCell().rowPosition, false, false, false); } // Cell features @@ -244,51 +242,102 @@ public class SelectionLayer extends AbstractIndexLayerTransform { } public void selectRegion(int startColumnPosition, int startRowPosition, int regionWidth, int regionHeight) { - if (lastSelectedRegion == null) { - lastSelectedRegion = new Rectangle(startColumnPosition, startRowPosition, regionWidth, regionHeight); + if (getLastSelectedRegion() == null) { + setLastSelectedRegion(new Rectangle(startColumnPosition, startRowPosition, regionWidth, regionHeight)); + } else { + setLastSelectedRegion(startColumnPosition, startRowPosition, regionWidth, regionHeight); + } + selectionModel.addSelection(new Rectangle(getLastSelectedRegion().x, getLastSelectedRegion().y, getLastSelectedRegion().width, getLastSelectedRegion().height)); + } + + protected void setLastSelectedRegion(Rectangle region) { + if (selectionModel instanceof IMarkerSelectionModel) { + ((IMarkerSelectionModel) selectionModel).setLastSelectedRegion(region); + } else { + lastSelectedRegion = region; + } + } + + protected void setLastSelectedRegion(int startColumnPosition, int startRowPosition, int regionWidth, int regionHeight) { + if (selectionModel instanceof IMarkerSelectionModel) { + ((IMarkerSelectionModel) selectionModel).setLastSelectedRegion(startColumnPosition, startRowPosition, regionWidth, regionHeight); } else { lastSelectedRegion.x = startColumnPosition; lastSelectedRegion.y = startRowPosition; lastSelectedRegion.width = regionWidth; lastSelectedRegion.height = regionHeight; } - selectionModel.addSelection(new Rectangle(lastSelectedRegion.x, lastSelectedRegion.y, lastSelectedRegion.width, lastSelectedRegion.height)); } // Selection anchor public PositionCoordinate getSelectionAnchor() { - return selectionAnchor; + if (selectionModel instanceof IMarkerSelectionModel) { + Point coordinate = ((IMarkerSelectionModel) selectionModel).getSelectionAnchor(); + return new PositionCoordinate(this, coordinate.x, coordinate.y); + } else { + return selectionAnchor; + } } public void moveSelectionAnchor(int startColumnPositionInRegion, int startRowPosition) { - selectionAnchor.columnPosition = startColumnPositionInRegion; - selectionAnchor.rowPosition = startRowPosition; + setSelectionAnchor(startColumnPositionInRegion, startRowPosition); } - + + void setSelectionAnchor(int columnPosition, int rowPosition) { + if (selectionModel instanceof IMarkerSelectionModel) { + ((IMarkerSelectionModel) selectionModel).setSelectionAnchor(new Point(columnPosition, rowPosition)); + } else { + selectionAnchor.columnPosition = columnPosition; + selectionAnchor.rowPosition = rowPosition; + } + } + // Last selected public PositionCoordinate getLastSelectedCellPosition() { - if (lastSelectedCell.columnPosition != NO_SELECTION && lastSelectedCell.rowPosition != NO_SELECTION) { - return lastSelectedCell; + PositionCoordinate coordinate = getLastSelectedCell(); + if (hasSelection(coordinate)) { + return coordinate; } else { return null; } } + PositionCoordinate getLastSelectedCell() { + if (selectionModel instanceof IMarkerSelectionModel) { + Point coordinate = ((IMarkerSelectionModel) selectionModel).getLastSelectedCell(); + return new PositionCoordinate(this, coordinate.x, coordinate.y); + } else { + return lastSelectedCell; + } + } + + static boolean hasSelection(PositionCoordinate coordinate) { + return coordinate.columnPosition != NO_SELECTION && coordinate.rowPosition != NO_SELECTION; + } + public void setLastSelectedCell(int columnPosition, int rowPosition) { - lastSelectedCell.columnPosition = columnPosition; - lastSelectedCell.rowPosition = rowPosition; + if (selectionModel instanceof IMarkerSelectionModel) { + ((IMarkerSelectionModel) selectionModel).setLastSelectedCell(new Point(columnPosition, rowPosition)); + } else { + lastSelectedCell.columnPosition = columnPosition; + lastSelectedCell.rowPosition = rowPosition; + } } public Rectangle getLastSelectedRegion() { - return lastSelectedRegion; + if (selectionModel instanceof IMarkerSelectionModel) { + return ((IMarkerSelectionModel) selectionModel).getLastSelectedRegion(); + } else { + return lastSelectedRegion; + } } // Column features public boolean hasColumnSelection() { - return lastSelectedCell.columnPosition != NO_SELECTION; + return getLastSelectedCell().columnPosition != NO_SELECTION; } public int[] getSelectedColumnPositions() { @@ -314,7 +363,7 @@ public class SelectionLayer extends AbstractIndexLayerTransform { // Row features public boolean hasRowSelection() { - return lastSelectedCell.rowPosition != NO_SELECTION; + return getLastSelectedCell().rowPosition != NO_SELECTION; } public int getSelectedRowCount() { @@ -361,14 +410,9 @@ public class SelectionLayer extends AbstractIndexLayerTransform { ILayerCell cell = getCellByPosition(columnPosition, rowPosition); if (cell != null) { - Rectangle cellRectangle = - new Rectangle( - cell.getOriginColumnPosition(), - cell.getOriginRowPosition(), - cell.getColumnSpan(), - cell.getRowSpan()); - - if (cellRectangle.contains(selectionAnchor.columnPosition, selectionAnchor.rowPosition)) { + Rectangle cellRectangle = new Rectangle(cell.getOriginColumnPosition(), cell.getOriginRowPosition(), cell.getColumnSpan(), cell.getRowSpan()); + + if (cellRectangle.contains(getSelectionAnchor().columnPosition, getSelectionAnchor().rowPosition)) { labelStack.addLabel(SelectionStyleLabels.SELECTION_ANCHOR_STYLE); } } diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/preserve/PreserveSelectionModel.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/preserve/PreserveSelectionModel.java new file mode 100644 index 00000000..7888ca4c --- /dev/null +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/preserve/PreserveSelectionModel.java @@ -0,0 +1,656 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +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.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.commons.lang.ArrayUtils; +import org.eclipse.nebula.widgets.nattable.coordinate.Range; +import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider; +import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor; +import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.selection.IMarkerSelectionModel; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.selection.preserve.Selections.CellPosition; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; + +/** + * Individual cell selection model that copes with the reordering of rows. + * + * @param <T> the type of object underlying each row + */ +public class PreserveSelectionModel<T> implements IMarkerSelectionModel { + + /** + * Provider of cell information + */ + private final IUniqueIndexLayer selectionLayer; + + /** + * Provider of underlying row objects + */ + private final IRowDataProvider<T> rowDataProvider; + + /** + * Provider of unique IDs for the rows + */ + private final IRowIdAccessor<T> rowIdAccessor; + + /** + * Whether to allow multiple selections + */ + private boolean allowMultiSelection; + + /** + * The selected cells + */ + private Selections<T> selections = new Selections<T>(); + + /** + * Lock for ensuring thread safety + */ + private final ReadWriteLock selectionsLock; + + /** + * Position of the selection anchor marker, expressed in row object and column position + */ + CellPosition<T> selectionAnchor; + + /** + * Position of the last selected cell marker, expressed in row object and column position + */ + CellPosition<T> lastSelectedCell; + + /** + * Area of the last selected region marker, expressed in row position, column position, + * number of column width and number of rows height + */ + Rectangle lastSelectedRegion; + + /** + * The row object of the origin of the last selected region + */ + T lastSelectedRegionOriginRowObject; + + /** + * Creates a row sortable selection model + * + * @param selectionLayer + * provider of cell information + * @param rowDataProvider + * provider of underlying row objects + * @param rowIdAccessor + * provider of unique IDs for the rows + */ + public PreserveSelectionModel(IUniqueIndexLayer selectionLayer, IRowDataProvider<T> rowDataProvider, IRowIdAccessor<T> rowIdAccessor) { + this.selectionLayer = selectionLayer; + this.rowDataProvider = rowDataProvider; + this.rowIdAccessor = rowIdAccessor; + this.allowMultiSelection = true; + selectionsLock = new ReentrantReadWriteLock(); + } + + @Override + public boolean isMultipleSelectionAllowed() { + return allowMultiSelection; + } + + @Override + public void setMultipleSelectionAllowed(boolean multipleSelectionAllowed) { + allowMultiSelection = multipleSelectionAllowed; + } + + @Override + public void addSelection(int columnPosition, int rowPosition) { + selectionsLock.writeLock().lock(); + try { + if (!allowMultiSelection) { + clearSelection(); + } + + T rowObject = getRowObjectByPosition(rowPosition); + if (rowObject != null) { + Serializable rowId = rowIdAccessor.getRowId(rowObject); + selections.select(rowId, rowObject, columnPosition); + } + } finally { + selectionsLock.writeLock().unlock(); + } + } + + @Override + public void addSelection(Rectangle range) { + selectionsLock.writeLock().lock(); + try { + SelectionOperation addSelectionOperation = new SelectionOperation() { + + @Override + public void run(int columnPosition, int rowPosition) { + addSelection(columnPosition, rowPosition); + } + }; + performOnKnownCells(range, addSelectionOperation); + } finally { + selectionsLock.writeLock().unlock(); + } + } + + @Override + public void clearSelection() { + selectionsLock.writeLock().lock(); + try { + selections.clear(); + } finally { + selectionsLock.writeLock().unlock(); + } + } + + @Override + public void clearSelection(int columnPosition, int rowPosition) { + selectionsLock.writeLock().lock(); + try { + T rowObject = getRowObjectByPosition(rowPosition); + if (rowObject != null) { + Serializable rowId = rowIdAccessor.getRowId(rowObject); + selections.deselect(rowId, columnPosition); + } + } finally { + selectionsLock.writeLock().unlock(); + } + } + + @Override + public void clearSelection(Rectangle removedSelection) { + selectionsLock.writeLock().lock(); + try { + SelectionOperation clearSelectionOperation = new SelectionOperation() { + + @Override + public void run(int columnPosition, int rowPosition) { + clearSelection(columnPosition, rowPosition); + } + }; + performOnKnownCells(removedSelection, clearSelectionOperation); + } finally { + selectionsLock.writeLock().unlock(); + } + } + + /** + * Only perform selection operations on cells which are known. + * + * For example selecting a full row operation will have a range from 0 to Integer.Max_Value. + * But only needed to operate on a range from 0 to total-column-count + * + * @param selection + * area which the operation should be run on. + * @param selectionOperation + * the operation to be perform on every cell in the area. + */ + private void performOnKnownCells(Rectangle selection, SelectionOperation selectionOperation) { + int columnCount = selectionLayer.getColumnCount(); + int rowCount = selectionLayer.getRowCount(); + int startColumnPosition = selection.x; + int startRowPosition = selection.y; + if (startColumnPosition < columnCount && startRowPosition < rowCount) { + + int columnStartIndex = selectionLayer.getColumnIndexByPosition(selection.x); + int numberOfVisibleColumnsToBeSelected = (selection.x + selection.width <= columnCount) ? selection.width : columnCount - columnStartIndex; + int rowStartIndex = selectionLayer.getRowIndexByPosition(selection.y); + int numberOfVisibleRowsToBeSelected = (selection.y + selection.height <= rowCount) ? selection.height : rowCount - rowStartIndex; + + for (int columnPosition = startColumnPosition; columnPosition < startColumnPosition + numberOfVisibleColumnsToBeSelected; columnPosition++) { + for (int rowPosition = startRowPosition; rowPosition < startRowPosition + numberOfVisibleRowsToBeSelected; rowPosition++) { + selectionOperation.run(columnPosition, rowPosition); + } + } + } + } + + @Override + public boolean isEmpty() { + selectionsLock.readLock().lock(); + try { + return selections.isEmpty(); + } finally { + selectionsLock.readLock().unlock(); + } + } + + @Override + public List<Rectangle> getSelections() { + ArrayList<Rectangle> selectedCells = new ArrayList<Rectangle>(); + + selectionsLock.readLock().lock(); + try { + for (CellPosition<T> cellPosition : selections.getSelections()) { + int rowPosition = getRowPositionByRowObject(cellPosition.getRowObject()); + if (isRowVisible(rowPosition)) { + Integer columnPosition = cellPosition.getColumnPosition(); + Rectangle selectedCell = new Rectangle(columnPosition, rowPosition, 1, 1); + selectedCells.add(selectedCell); + } + } + } finally { + selectionsLock.readLock().unlock(); + } + return selectedCells; + } + + /** + * Determines if rowPosition represents a visible row + * @param rowPosition position of row to inspect + * @return whether rowPosition represents a visible row + */ + private boolean isRowVisible(int rowPosition) { + return rowPosition != -1; + } + + @Override + public boolean isCellPositionSelected(int columnPosition, int rowPosition) { + selectionsLock.readLock().lock(); + + try { + ILayerCell cell = selectionLayer.getCellByPosition(columnPosition, rowPosition); + int cellOriginRowPosition = cell.getOriginRowPosition(); + + for (int candidateRowPosition = cellOriginRowPosition; candidateRowPosition < cellOriginRowPosition + cell.getRowSpan(); candidateRowPosition++) { + Serializable rowId = getRowIdByPosition(candidateRowPosition); + + int cellOriginColumnPosition = cell.getOriginColumnPosition(); + for (int candidateColumnPosition = cellOriginColumnPosition; candidateColumnPosition < cellOriginColumnPosition + cell.getColumnSpan(); candidateColumnPosition++) { + if (selections.isSelected(rowId, candidateColumnPosition)) { + return true; + } + + } + } + } finally { + selectionsLock.readLock().unlock(); + } + return false; + } + + @Override + public int[] getSelectedColumnPositions() { + selectionsLock.readLock().lock(); + try { + Collection<Integer> columnPositions = selections.getColumnPositions(); + return ArrayUtils.toPrimitive(columnPositions.toArray(new Integer[columnPositions.size()])); + } finally { + selectionsLock.readLock().unlock(); + } + } + + @Override + public boolean isColumnPositionSelected(int columnPosition) { + selectionsLock.readLock().lock(); + try { + for (Selections<T>.Row row : selections.getRows()) { + if (row.contains(columnPosition)) { + return true; + } + } + + } finally { + selectionsLock.readLock().unlock(); + } + return false; + } + + @Override + public int[] getFullySelectedColumnPositions(int columnHeight) { + selectionsLock.readLock().lock(); + try { + List<Integer> fullySelectedColumnPositions = new ArrayList<Integer>(); + for (Integer selectedColumn : selections.getColumnPositions()) { + if (isColumnPositionFullySelected(selectedColumn, columnHeight)) { + fullySelectedColumnPositions.add(selectedColumn); + } + } + return ArrayUtils.toPrimitive(fullySelectedColumnPositions.toArray(new Integer[fullySelectedColumnPositions.size()])); + } finally { + selectionsLock.readLock().unlock(); + } + } + + @Override + public boolean isColumnPositionFullySelected(int columnPosition, int columnHeight) { + TreeSet<Integer> selectedRowIndices = new TreeSet<Integer>(); + selectionsLock.readLock().lock(); + try { + Selections<T>.Column selectedRowsInColumn = selections.getSelectedRows(columnPosition); + if (hasColumnsSelectedRows(selectedRowsInColumn)) { + for (Serializable rowId : selectedRowsInColumn.getItems()) { + Selections<T>.Row row = selections.getSelectedColumns(rowId); + T rowObject = row.getRowObject(); + int rowIndex = rowDataProvider.indexOfRowObject(rowObject); + selectedRowIndices.add(rowIndex); + } + } + return hasContinuousSection(selectedRowIndices, columnHeight); + } finally { + selectionsLock.readLock().unlock(); + } + } + + /** + * Determines if there are selected cells in a column + * @param column collections of selected cells for a column + * @return whether there are selected cells in column + */ + private boolean hasColumnsSelectedRows(Selections<T>.Column column) { + return column != null; + } + + /** + * Determines if there is a long enough continuous section of integers in the + * sequence. The continuous section must be at least sectionSize long. + * @param sequence sequence of integers to inspect + * @param minimumLength minimum length of continuous section + * @return whether there is a long enough continuous section of integers in + * sequence + */ + private boolean hasContinuousSection(TreeSet<Integer> sequence, int minimumLength) { + int counter = 0; + Integer previousValue = null; + for (Integer index : sequence) { + if (previousValue != null) { + // Not first measurement. + if (index != previousValue + 1) { + // Restart measurement: + counter = 0; + } + } + // Continuous measurement: + previousValue = index; + counter += 1; + if (counter == minimumLength) { + return true; + } + } + return false; + } + + @Override + public int getSelectedRowCount() { + selectionsLock.readLock().lock(); + try { + return selections.getRows().size(); + } finally { + selectionsLock.readLock().unlock(); + } + } + + @Override + public Set<Range> getSelectedRowPositions() { + HashSet<Range> visiblySelectedRowPositions = new HashSet<Range>(); + + selectionsLock.readLock().lock(); + try { + for (Selections<T>.Row row : selections.getRows()) { + int rowPosition = getRowPositionByRowObject(row.getRowObject()); + if (isRowVisible(rowPosition)) { + visiblySelectedRowPositions.add(new Range(rowPosition, rowPosition + 1)); + } + } + } finally { + selectionsLock.readLock().unlock(); + } + return visiblySelectedRowPositions; + } + + @Override + public boolean isRowPositionSelected(int rowPosition) { + selectionsLock.readLock().lock(); + try { + Serializable rowId = getRowIdByPosition(rowPosition); + return selections.isRowSelected(rowId); + } finally { + selectionsLock.readLock().unlock(); + } + } + + @Override + public int[] getFullySelectedRowPositions(int rowWidth) { + selectionsLock.readLock().lock(); + try { + List<Integer> fullySelectedRows = new ArrayList<Integer>(); + for (Selections<T>.Row selectedRow : selections.getRows()) { + T rowObject = selectedRow.getRowObject(); + int rowPosition = getRowPositionByRowObject(rowObject); + if (isRowVisible(rowPosition) && isRowPositionFullySelected(rowPosition, rowWidth)) { + fullySelectedRows.add(rowPosition); + } + } + Collections.sort(fullySelectedRows); + return ArrayUtils.toPrimitive(fullySelectedRows.toArray(new Integer[fullySelectedRows.size()])); + } finally { + selectionsLock.readLock().unlock(); + } + } + + @Override + public boolean isRowPositionFullySelected(int rowPosition, int rowWidth) { + TreeSet<Integer> selectedColumnPositions = new TreeSet<Integer>(); + + selectionsLock.readLock().lock(); + try { + T rowObject = getRowObjectByPosition(rowPosition); + if (rowObject != null) { + Serializable rowId = rowIdAccessor.getRowId(rowObject); + Selections<T>.Row selectedColumnsInRow = selections.getSelectedColumns(rowId); + if (hasRowSelectedColumns(selectedColumnsInRow)) { + for (Integer columnPosition : selectedColumnsInRow.getItems()) { + selectedColumnPositions.add(columnPosition); + } + } + } + } finally { + selectionsLock.readLock().unlock(); + } + return hasContinuousSection(selectedColumnPositions, rowWidth); + } + + /** + * Determines if there are selected cells in a row + * @param row collections of selected cells for a row + * @return whether there are selected cells in row + */ + private boolean hasRowSelectedColumns(Selections<T>.Row row) { + return row != null; + } + + /** + * Retrieves the row ID for a row position + * @param rowPosition row position for retrieving row ID + * @return row ID for rowPosition, or null if undefined + */ + private Serializable getRowIdByPosition(int rowPosition) { + T rowObject = getRowObjectByPosition(rowPosition); + if (rowObject != null) { + return rowIdAccessor.getRowId(rowObject); + } + return null; + } + + /** + * Retrieves the row object for a row position + * @param rowPosition row position for retrieving row object + * @return row object for rowPosition, or null if undefined + */ + private T getRowObjectByPosition(int rowPosition) { + int rowIndex = selectionLayer.getRowIndexByPosition(rowPosition); + if (rowIndex >= 0) { + try { + return rowDataProvider.getRowObject(rowIndex); + } catch (Exception e) { + // row index is invalid for the data provider + } + } + return null; + } + + /** + * Retrieves the row position for a row object + * @param rowObject row object for retrieving row position + * @return row position for rowObject, or -1 if undefined + */ + private int getRowPositionByRowObject(T rowObject) { + int rowIndex = rowDataProvider.indexOfRowObject(rowObject); + if (rowIndex == -1) { + return -1; + } + return selectionLayer.getRowPositionByIndex(rowIndex); + } + + @Override + public Point getSelectionAnchor() { + selectionsLock.readLock().lock(); + try { + return createMarkerPoint(selectionAnchor); + } finally { + selectionsLock.readLock().unlock(); + } + } + + @Override + public Point getLastSelectedCell() { + selectionsLock.readLock().lock(); + try { + return createMarkerPoint(lastSelectedCell); + } finally { + selectionsLock.readLock().unlock(); + } + } + + /** + * Creates a point from a cell position. The point is expressed in row position and + * column position. The row position is calculated by translating the row object of + * the cell position. It uses the column position of the cell position without + * translation. + * @param cellPosition cell position to translate into a point + * @return cellPosition expressed in row position and column position + */ + private Point createMarkerPoint(CellPosition<T> cellPosition) { + if (cellPosition == null) { + return createUndefinedPoint(); + } + int rowPosition = getRowPositionByRowObject(cellPosition.getRowObject()); + return new Point(cellPosition.getColumnPosition(), rowPosition); + } + + /** + * Creates an undefined point, using the SelectionLayer.NO_SELECTION constant. + * @return an undefined point + */ + private Point createUndefinedPoint() { + return new Point(SelectionLayer.NO_SELECTION, SelectionLayer.NO_SELECTION); + } + + @Override + public Rectangle getLastSelectedRegion() { + selectionsLock.readLock().lock(); + try { + if (lastSelectedRegion == null) { + return null; + } else { + correctLastSelectedRegion(); + return lastSelectedRegion; + } + } finally { + selectionsLock.readLock().unlock(); + } + } + + /** + * Corrects the last selected region by moving it so that it is originating on the row + * position identified by the last selected region origin row object. + */ + private void correctLastSelectedRegion() { + lastSelectedRegion.y = getRowPositionByRowObject(lastSelectedRegionOriginRowObject); + } + + @Override + public void setSelectionAnchor(Point coordinate) { + selectionsLock.writeLock().lock(); + try { + selectionAnchor = new CellPosition<T>(getRowObjectByPosition(coordinate.y), coordinate.x); + } finally { + selectionsLock.writeLock().unlock(); + } + } + + @Override + public void setLastSelectedCell(Point coordinate) { + selectionsLock.writeLock().lock(); + try { + lastSelectedCell = new CellPosition<T>(getRowObjectByPosition(coordinate.y), coordinate.x); + } finally { + selectionsLock.writeLock().unlock(); + } + } + + @Override + public void setLastSelectedRegion(Rectangle region) { + selectionsLock.writeLock().lock(); + try { + lastSelectedRegion = region; + if (region != null) { + lastSelectedRegionOriginRowObject = getRowObjectByPosition(region.y); + } + } finally { + selectionsLock.writeLock().unlock(); + } + } + + @Override + public void setLastSelectedRegion(int x, int y, int width, int height) { + selectionsLock.writeLock().lock(); + try { + lastSelectedRegion.x = x; + lastSelectedRegion.y = y; + lastSelectedRegion.width = width; + lastSelectedRegion.height = height; + + lastSelectedRegionOriginRowObject = getRowObjectByPosition(y); + } finally { + selectionsLock.writeLock().unlock(); + } + } + + /** + * Internal interface to be used for higher order methods. + * + */ + abstract interface SelectionOperation { + /** + * Performs the operation + * + * @param columnPosition + * @param rowPosition + */ + public void run(int columnPosition, int rowPosition); + } + +} diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/preserve/Selections.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/preserve/Selections.java new file mode 100644 index 00000000..8b5eef22 --- /dev/null +++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/preserve/Selections.java @@ -0,0 +1,389 @@ +/******************************************************************************* + * 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 + ******************************************************************************/ +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> selectedRows = new HashMap<Serializable, Row>(); + + /** + * 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 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 row = getSelectedColumns(rowId); + if (row != null) { + row.removeItem(columnPosition); + if (!row.hasSelection()) { + selectedRows.remove(rowId); + } + } + + Column column = getSelectedRows(columnPosition); + if (column != null) { + column.removeItem(rowId); + if (!column.hasSelection()) { + selectedColumns.remove(columnPosition); + } + } + } + + /** + * Removes all cell selections. + */ + void clear() { + selectedRows.clear(); + selectedColumns.clear(); + } + + /** + * Retrieves all rows that have selected cells. + * @return all rows that have selected cells + */ + Collection<Row> getRows() { + return 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>(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 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 getSelectedColumns(Serializable rowId) { + return 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 row : 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 selectedRows.containsKey(rowId); + } + + /** + * Determines whether there are selected cells + * @return whether there are selected cells + */ + boolean isEmpty() { + return 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 retrieveRow(Serializable rowId, T rowObject) { + Row row = getSelectedColumns(rowId); + if (row == null) { + row = new Row(rowId, rowObject); + 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); + 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). + */ + class Row extends Line<Serializable, Integer> { + /** + * The underlying row object + */ + private final T rowObject; + + /** + * Creates a row with the specified row + * @param rowId ID of the row + * @param rowObject underlying row object + */ + Row(Serializable rowId, T rowObject) { + super(rowId); + this.rowObject = rowObject; + } + + /** + * Retrieves the underlying row object + * @return the underlying row object + */ + T getRowObject() { + return 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). + */ + 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 content; + } + + /** + * Adds an item to the selected items + * @param item item to add + */ + void addItem(S item) { + content.add(item); + } + + /** + * Removes an item from the selected items + * @param item item to remove + */ + void removeItem(S item) { + content.remove(item); + } + + /** + * 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 content.contains(item); + } + + /** + * Determines whether the line has any selections + * @return whether the line has any selections + */ + boolean hasSelection() { + return !content.isEmpty(); + } + + /** + * Retrieves the line identifier + * @return the line identifier + */ + I getId() { + return 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 + columnPosition; + result = prime * result + ((rowObject == null) ? 0 : 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 (columnPosition != other.columnPosition) + return false; + if (rowObject == null) { + if (other.rowObject != null) + return false; + } else if (!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 rowObject; + } + + /** + * Retrieves the column position + * @return the column position + */ + Integer getColumnPosition() { + return columnPosition; + } + } + +} |