| /******************************************************************************* |
| * Copyright (c) 2012, 2020 Original authors and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Original authors and others - initial API and implementation |
| * Dirk Fauth <dirk.fauth@googlemail.com> - Bug 451217 |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.freeze; |
| |
| import java.util.Properties; |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.nebula.widgets.nattable.command.ILayerCommand; |
| import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate; |
| import org.eclipse.nebula.widgets.nattable.freeze.command.FreezeCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.freeze.config.DefaultFreezeGridBindings; |
| import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand; |
| import org.eclipse.nebula.widgets.nattable.grid.layer.DimensionallyDependentIndexLayer; |
| import org.eclipse.nebula.widgets.nattable.group.command.ViewportSelectColumnGroupCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.group.command.ViewportSelectRowGroupCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayer; |
| import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.event.ColumnStructuralChangeEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.RowStructuralChangeEvent; |
| import org.eclipse.nebula.widgets.nattable.painter.layer.CompositeFreezeLayerPainter; |
| import org.eclipse.nebula.widgets.nattable.persistence.IPersistable; |
| import org.eclipse.nebula.widgets.nattable.resize.event.ColumnResizeEvent; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; |
| import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportSelectColumnCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportSelectRowCommandHandler; |
| import org.eclipse.swt.graphics.Point; |
| |
| public class CompositeFreezeLayer extends CompositeLayer implements IUniqueIndexLayer { |
| |
| private final FreezeLayer freezeLayer; |
| private final ViewportLayer viewportLayer; |
| private final SelectionLayer selectionLayer; |
| |
| public CompositeFreezeLayer(FreezeLayer freezeLayer, |
| ViewportLayer viewportLayer, SelectionLayer selectionLayer) { |
| this(freezeLayer, viewportLayer, selectionLayer, true); |
| } |
| |
| public CompositeFreezeLayer(FreezeLayer freezeLayer, |
| ViewportLayer viewportLayer, SelectionLayer selectionLayer, |
| boolean useDefaultConfiguration) { |
| super(2, 2); |
| this.freezeLayer = freezeLayer; |
| this.viewportLayer = viewportLayer; |
| this.selectionLayer = selectionLayer; |
| |
| setChildLayer("FROZEN_REGION", freezeLayer, 0, 0); //$NON-NLS-1$ |
| setChildLayer("FROZEN_ROW_REGION", //$NON-NLS-1$ |
| new DimensionallyDependentIndexLayer( |
| viewportLayer.getScrollableLayer(), viewportLayer, freezeLayer), |
| 1, 0); |
| setChildLayer("FROZEN_COLUMN_REGION", //$NON-NLS-1$ |
| new DimensionallyDependentIndexLayer( |
| viewportLayer.getScrollableLayer(), freezeLayer, viewportLayer), |
| 0, 1); |
| setChildLayer("NONFROZEN_REGION", viewportLayer, 1, 1); //$NON-NLS-1$ |
| |
| this.layerPainter = new CompositeFreezeLayerPainter(this); |
| |
| registerCommandHandlers(); |
| |
| if (useDefaultConfiguration) { |
| addConfiguration(new DefaultFreezeGridBindings()); |
| } |
| } |
| |
| @Override |
| public void handleLayerEvent(ILayerEvent event) { |
| // Bug 451217 |
| // if structural change events are fired that carry no explicit diff |
| // information it is likely that the event handlers in the underlying |
| // layers are not executed. The following code is intended to "repair" |
| // possible inconsistent freeze-viewport states |
| if (event instanceof RowStructuralChangeEvent |
| && (((RowStructuralChangeEvent) event).getRowDiffs() == null |
| || ((RowStructuralChangeEvent) event).getRowDiffs().isEmpty())) { |
| if (this.viewportLayer.getMinimumOriginRowPosition() < this.freezeLayer.getRowCount()) { |
| this.viewportLayer.setMinimumOriginY(this.freezeLayer.getHeight()); |
| } |
| } |
| if (event instanceof ColumnStructuralChangeEvent |
| && (((ColumnStructuralChangeEvent) event).getColumnDiffs() == null |
| || ((ColumnStructuralChangeEvent) event).getColumnDiffs().isEmpty())) { |
| if (this.viewportLayer.getMinimumOriginColumnPosition() < this.freezeLayer.getColumnCount()) { |
| this.viewportLayer.setMinimumOriginX(this.freezeLayer.getWidth()); |
| } |
| } |
| // Bug 470061 |
| // in case the all columns are frozen, we also need to "repair" |
| // the inconsistent freeze-viewport states, as the viewport is not |
| // able to update itself since it doesn't handle the structural change |
| // event |
| else if (event instanceof ColumnResizeEvent |
| && this.freezeLayer.getColumnCount() == this.selectionLayer.getColumnCount() |
| && this.viewportLayer.getMinimumOriginColumnPosition() < this.freezeLayer.getColumnCount()) { |
| this.viewportLayer.setMinimumOriginX(this.freezeLayer.getWidth()); |
| } |
| |
| super.handleLayerEvent(event); |
| } |
| |
| public boolean isFrozen() { |
| return this.freezeLayer.isFrozen(); |
| } |
| |
| @Override |
| protected void registerCommandHandlers() { |
| registerCommandHandler(new FreezeCommandHandler(this.freezeLayer, this.viewportLayer, this.selectionLayer)); |
| |
| final DimensionallyDependentIndexLayer frozenRowLayer = |
| (DimensionallyDependentIndexLayer) getChildLayerByLayoutCoordinate(1, 0); |
| frozenRowLayer.registerCommandHandler(new ViewportSelectRowCommandHandler(frozenRowLayer)); |
| frozenRowLayer.registerCommandHandler(new ViewportSelectRowGroupCommandHandler(frozenRowLayer)); |
| |
| final DimensionallyDependentIndexLayer frozenColumnLayer = |
| (DimensionallyDependentIndexLayer) getChildLayerByLayoutCoordinate(0, 1); |
| frozenColumnLayer.registerCommandHandler(new ViewportSelectColumnCommandHandler(frozenColumnLayer)); |
| frozenColumnLayer.registerCommandHandler(new ViewportSelectColumnGroupCommandHandler(frozenColumnLayer)); |
| } |
| |
| @Override |
| public boolean doCommand(ILayerCommand command) { |
| // if this layer should handle a ClientAreaResizeCommand we have to |
| // ensure that it is only called on the ViewportLayer, as otherwise |
| // an undefined behaviour could occur because the ViewportLayer |
| // isn't informed about potential refreshes |
| if (command instanceof ClientAreaResizeCommand) { |
| this.viewportLayer.doCommand(command); |
| } |
| return super.doCommand(command); |
| } |
| |
| @Override |
| public int getColumnPositionByIndex(int columnIndex) { |
| int columnPosition = this.freezeLayer.getColumnPositionByIndex(columnIndex); |
| if (columnPosition >= 0) { |
| return columnPosition; |
| } |
| return this.freezeLayer.getColumnCount() |
| + this.viewportLayer.getColumnPositionByIndex(columnIndex); |
| } |
| |
| @Override |
| public int getRowPositionByIndex(int rowIndex) { |
| int rowPosition = this.freezeLayer.getRowPositionByIndex(rowIndex); |
| if (rowPosition >= 0) { |
| return rowPosition; |
| } |
| return this.freezeLayer.getRowCount() |
| + this.viewportLayer.getRowPositionByIndex(rowIndex); |
| } |
| |
| @Override |
| protected int getLayoutXByColumnPosition(int compositeColumnPosition) { |
| if (compositeColumnPosition < 0 || compositeColumnPosition >= getColumnCount()) { |
| return 1; |
| } |
| return super.getLayoutXByColumnPosition(compositeColumnPosition); |
| } |
| |
| @Override |
| protected int getLayoutYByRowPosition(int compositeRowPosition) { |
| if (compositeRowPosition < 0 || compositeRowPosition >= getRowCount()) { |
| return 1; |
| } |
| return super.getLayoutYByRowPosition(compositeRowPosition); |
| } |
| |
| @Override |
| protected Point getLayoutXYByPosition(int compositeColumnPosition, int compositeRowPosition) { |
| int layoutX = getLayoutXByColumnPosition(compositeColumnPosition); |
| int layoutY = getLayoutYByRowPosition(compositeRowPosition); |
| |
| if (layoutX < 0 || layoutY < 0) { |
| return null; |
| } |
| |
| return new Point(layoutX, layoutY); |
| } |
| |
| /** |
| * This method is used to determine the bounds of a cell with column span in |
| * case of an active freeze. This is needed because column positions can be |
| * ambiguous if the start column of a spanned cell is moved below the frozen |
| * area. |
| * |
| * @param columnPosition |
| * The column position that was used to retrieve the cell. Needed |
| * to identify the origin layout in order to know if the start |
| * needs to be searched in the frozen or the scrollable area. |
| * @param startColumn |
| * The start column position of the spanned cell. |
| * @param endColumn |
| * The end column position of the spanned cell. |
| * @return int array that contains the start x position of a cell in the |
| * first element, and the width of the cell in the second element. |
| * @since 1.6 |
| */ |
| public int[] getColumnBounds(int columnPosition, int startColumn, int endColumn) { |
| int columnPositionLayout = getLayoutXByColumnPosition(columnPosition); |
| int startColumnLayout = getLayoutXByColumnPosition(startColumn); |
| int endColumnLayout = getLayoutXByColumnPosition(endColumn); |
| |
| int start = startColumn; |
| int end = isFrozen() && endColumnLayout == 1 ? endColumn - this.viewportLayer.getScrollableLayer().getColumnPositionByX(this.viewportLayer.getOrigin().getX()) : endColumn; |
| ILayer startLayer = null; |
| |
| int startX = 0; |
| int width = 0; |
| if (columnPositionLayout == endColumnLayout) { |
| // if a column in the same layout was requested where the end |
| // position is, we use the same layout for calculating start/end |
| |
| if (endColumnLayout == 0 || startColumn == columnPosition) { |
| startX = getStartXOfColumnPosition(startColumn); |
| int column = startColumn; |
| for (; column <= endColumn; column++) { |
| width += getColumnWidthByPosition(column); |
| } |
| } else { |
| startLayer = getChildLayerByLayoutCoordinate(endColumnLayout, 1); |
| start = start - this.viewportLayer.getMinimumOriginColumnPosition(); |
| end = endColumn - this.viewportLayer.getMinimumOriginColumnPosition(); |
| startX = this.freezeLayer.getWidth() + startLayer.getStartXOfColumnPosition(start); |
| int column = start; |
| for (; column <= end; column++) { |
| width += this.viewportLayer.getColumnWidthByPosition(column); |
| } |
| } |
| } else { |
| startLayer = getChildLayerByLayoutCoordinate(startColumnLayout, 1); |
| startX = this.freezeLayer.getStartXOfColumnPosition(start); |
| int freezeWidth = 0; |
| int column = start; |
| for (; column < this.freezeLayer.getColumnCount(); column++) { |
| freezeWidth += this.freezeLayer.getColumnWidthByPosition(column); |
| } |
| |
| int endX = this.freezeLayer.getWidth() + this.viewportLayer.getStartXOfColumnPosition(end) + this.viewportLayer.getColumnWidthByPosition(end); |
| width = Math.max(freezeWidth, endX - startX); |
| } |
| |
| return new int[] { startX, width }; |
| } |
| |
| /** |
| * This method is used to determine the bounds of a cell with row span in |
| * case of an active freeze. This is needed because row positions can be |
| * ambiguous if the start row of a spanned cell is moved below the frozen |
| * area. |
| * |
| * @param rowPosition |
| * The row position that was used to retrieve the cell. Needed to |
| * identify the origin layout in order to know if the start needs |
| * to be searched in the frozen or the scrollable area. |
| * @param startRow |
| * The start row position of the spanned cell. |
| * @param endRow |
| * The end row position of the spanned cell. |
| * @return int array that contains the start y position of a cell in the |
| * first element, and the height of the cell in the second element. |
| * @since 1.6 |
| */ |
| public int[] getRowBounds(int rowPosition, int startRow, int endRow) { |
| int rowPositionLayout = getLayoutYByRowPosition(rowPosition); |
| int startRowLayout = getLayoutYByRowPosition(startRow); |
| int endRowLayout = getLayoutYByRowPosition(endRow); |
| |
| int start = startRow; |
| int end = isFrozen() && endRowLayout == 1 ? endRow - this.viewportLayer.getScrollableLayer().getRowPositionByY(this.viewportLayer.getOrigin().getY()) : endRow; |
| ILayer startLayer = null; |
| |
| int startY = 0; |
| int height = 0; |
| if (rowPositionLayout == endRowLayout) { |
| // if a row in the same layout was requested where the end |
| // position is, we use the same layout for calculating start/end |
| |
| if (endRowLayout == 0 || startRow == rowPosition) { |
| startY = getStartYOfRowPosition(startRow); |
| int row = startRow; |
| for (; row <= endRow; row++) { |
| height += getRowHeightByPosition(row); |
| } |
| } else { |
| startLayer = getChildLayerByLayoutCoordinate(1, endRowLayout); |
| start = start - this.viewportLayer.getMinimumOriginRowPosition(); |
| end = endRow - this.viewportLayer.getMinimumOriginRowPosition(); |
| startY = this.freezeLayer.getHeight() + startLayer.getStartYOfRowPosition(start); |
| int row = start; |
| for (; row <= end; row++) { |
| height += this.viewportLayer.getRowHeightByPosition(row); |
| } |
| } |
| } else { |
| startLayer = getChildLayerByLayoutCoordinate(1, startRowLayout); |
| startY = this.freezeLayer.getStartYOfRowPosition(start); |
| int freezeHeight = 0; |
| int row = start; |
| for (; row < this.freezeLayer.getRowCount(); row++) { |
| freezeHeight += this.freezeLayer.getRowHeightByPosition(row); |
| } |
| |
| int endY = this.freezeLayer.getHeight() + this.viewportLayer.getStartYOfRowPosition(end) + this.viewportLayer.getRowHeightByPosition(end); |
| height = Math.max(freezeHeight, endY - startY); |
| } |
| |
| return new int[] { startY, height }; |
| } |
| |
| // Persistence |
| |
| @Override |
| public void saveState(String prefix, Properties properties) { |
| PositionCoordinate coord = this.freezeLayer.getTopLeftPosition(); |
| properties.setProperty( |
| prefix + FreezeLayer.PERSISTENCE_TOP_LEFT_POSITION, |
| coord.columnPosition + IPersistable.VALUE_SEPARATOR + coord.rowPosition); |
| |
| coord = this.freezeLayer.getBottomRightPosition(); |
| properties.setProperty( |
| prefix + FreezeLayer.PERSISTENCE_BOTTOM_RIGHT_POSITION, |
| coord.columnPosition + IPersistable.VALUE_SEPARATOR + coord.rowPosition); |
| |
| super.saveState(prefix, properties); |
| } |
| |
| @Override |
| public void loadState(String prefix, Properties properties) { |
| String property = properties.getProperty( |
| prefix + FreezeLayer.PERSISTENCE_TOP_LEFT_POSITION); |
| PositionCoordinate topLeftPosition = null; |
| if (property != null) { |
| StringTokenizer tok = new StringTokenizer(property, |
| IPersistable.VALUE_SEPARATOR); |
| String columnPosition = tok.nextToken(); |
| String rowPosition = tok.nextToken(); |
| topLeftPosition = new PositionCoordinate( |
| this.freezeLayer, |
| Integer.valueOf(columnPosition), |
| Integer.valueOf(rowPosition)); |
| } |
| |
| property = properties.getProperty( |
| prefix + FreezeLayer.PERSISTENCE_BOTTOM_RIGHT_POSITION); |
| PositionCoordinate bottomRightPosition = null; |
| if (property != null) { |
| StringTokenizer tok = |
| new StringTokenizer(property, IPersistable.VALUE_SEPARATOR); |
| String columnPosition = tok.nextToken(); |
| String rowPosition = tok.nextToken(); |
| bottomRightPosition = new PositionCoordinate( |
| this.freezeLayer, |
| Integer.valueOf(columnPosition), |
| Integer.valueOf(rowPosition)); |
| } |
| |
| // only restore a freeze state if there is one persisted |
| if (topLeftPosition != null && bottomRightPosition != null) { |
| if (topLeftPosition.columnPosition == -1 |
| && topLeftPosition.rowPosition == -1 |
| && bottomRightPosition.columnPosition == -1 |
| && bottomRightPosition.rowPosition == -1) { |
| FreezeHelper.unfreeze(this.freezeLayer, this.viewportLayer); |
| } else { |
| FreezeHelper.freeze(this.freezeLayer, this.viewportLayer, |
| topLeftPosition, bottomRightPosition); |
| } |
| } |
| |
| super.loadState(prefix, properties); |
| } |
| |
| } |