| /******************************************************************************* |
| * Copyright (c) 2012, 2021 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 462143 |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.viewport; |
| |
| import org.eclipse.nebula.widgets.nattable.NatTable; |
| import org.eclipse.nebula.widgets.nattable.command.ILayerCommand; |
| import org.eclipse.nebula.widgets.nattable.coordinate.PixelCoordinate; |
| import org.eclipse.nebula.widgets.nattable.coordinate.Range; |
| import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand; |
| 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.AbstractLayerTransform; |
| import org.eclipse.nebula.widgets.nattable.layer.IDpiConverter; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayer; |
| import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.command.ConfigureScalingCommand; |
| import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; |
| import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent; |
| import org.eclipse.nebula.widgets.nattable.print.command.PrintEntireGridCommand; |
| import org.eclipse.nebula.widgets.nattable.print.command.TurnViewportOffCommand; |
| import org.eclipse.nebula.widgets.nattable.print.command.TurnViewportOnCommand; |
| import org.eclipse.nebula.widgets.nattable.resize.event.ColumnResizeEvent; |
| import org.eclipse.nebula.widgets.nattable.resize.event.RowResizeEvent; |
| import org.eclipse.nebula.widgets.nattable.selection.ScrollSelectionCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum; |
| import org.eclipse.nebula.widgets.nattable.selection.command.MoveSelectionCommand; |
| import org.eclipse.nebula.widgets.nattable.selection.command.ScrollSelectionCommand; |
| import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent; |
| import org.eclipse.nebula.widgets.nattable.selection.event.ColumnSelectionEvent; |
| import org.eclipse.nebula.widgets.nattable.selection.event.RowSelectionEvent; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.RecalculateScrollBarsCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.ShowCellInViewportCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.ShowColumnInViewportCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.ShowRowInViewportCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportDragCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportSelectColumnCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportSelectRowCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.viewport.event.ScrollEvent; |
| import org.eclipse.nebula.widgets.nattable.viewport.event.ViewportEventHandler; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.ScrollBar; |
| import org.eclipse.swt.widgets.Scrollable; |
| |
| /** |
| * Viewport - the visible area of NatTable Places a 'viewport' over the table. |
| * Introduces scroll bars over the table and keeps them in sync with the data |
| * being displayed. This is typically placed over the {@link SelectionLayer}. |
| */ |
| public class ViewportLayer extends AbstractLayerTransform implements IUniqueIndexLayer { |
| |
| private static final int EDGE_HOVER_REGION_SIZE = 12; |
| |
| private HorizontalScrollBarHandler hBarListener; |
| private VerticalScrollBarHandler vBarListener; |
| private final IUniqueIndexLayer scrollableLayer; |
| |
| private IScroller<?> horizontalScroller; |
| private IScroller<?> verticalScroller; |
| |
| private boolean horizontalScrollbarEnabled = true; |
| private boolean verticalScrollbarEnabled = true; |
| |
| // The viewport origin, in scrollable pixel coordinates. |
| private PixelCoordinate origin = new PixelCoordinate(0, 0); |
| private PixelCoordinate minimumOrigin = new PixelCoordinate(0, 0); |
| private int minimumOriginColumnPosition = 0; |
| private int minimumOriginRowPosition = 0; |
| private boolean viewportOff = false; |
| private PixelCoordinate savedOrigin = new PixelCoordinate(0, 0); |
| |
| // split viewport support |
| /** |
| * Only used for split viewport support to configure the maximum column |
| * position this viewport instance should handle. If set to a positive |
| * value, column positions to the right will not be handled. |
| */ |
| private int maxColumnPosition = -1; |
| /** |
| * Only used for split viewport support to configure the minimum column |
| * position this viewport instance should handle. If set to a positive |
| * value, column positions to the left will not be handled. |
| */ |
| private int minColumnPosition = -1; |
| /** |
| * Only used for split viewport support to configure the maximum row |
| * position this viewport instance should handle. If set to a positive |
| * value, row positions to the bottom will not be handled. |
| */ |
| private int maxRowPosition = -1; |
| /** |
| * Only used for split viewport support to configure the minimum row |
| * position this viewport instance should handle. If set to a positive |
| * value, row positions to the top will not be handled. |
| */ |
| private int minRowPosition = -1; |
| |
| // Cache |
| private int cachedColumnCount = -1; |
| private int cachedRowCount = -1; |
| private int cachedClientAreaWidth = 0; |
| private int cachedClientAreaHeight = 0; |
| private int cachedWidth = -1; |
| private int cachedHeight = -1; |
| |
| /** |
| * Row position of the row in the underlying layer that should be kept |
| * visible inside the viewport or -1 if no special row should be kept. |
| */ |
| private int keepInViewportRowPosition = -1; |
| |
| // Edge hover scrolling |
| |
| private MoveViewportRunnable edgeHoverRunnable; |
| |
| private IDpiConverter horizontalDpiConverter; |
| private IDpiConverter verticalDpiConverter; |
| |
| public ViewportLayer(IUniqueIndexLayer underlyingLayer) { |
| super(underlyingLayer); |
| this.scrollableLayer = underlyingLayer; |
| |
| registerCommandHandlers(); |
| |
| registerEventHandler(new ViewportEventHandler(this)); |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| |
| if (this.hBarListener != null) { |
| this.hBarListener.dispose(); |
| this.hBarListener = null; |
| } |
| |
| if (this.vBarListener != null) { |
| this.vBarListener.dispose(); |
| this.vBarListener = null; |
| } |
| |
| cancelEdgeHoverScroll(); |
| } |
| |
| /** |
| * Set a different horizontal scroller than the default one. |
| * |
| * @param scroller |
| * The scroller that should be used for horizontal scrolling. |
| */ |
| public void setHorizontalScroller(IScroller<?> scroller) { |
| this.horizontalScroller = scroller; |
| // ensure to dispose and remove the already registered listener |
| if (this.hBarListener != null) { |
| this.hBarListener.dispose(); |
| this.hBarListener = null; |
| } |
| } |
| |
| /** |
| * Set a different vertical scroller than the default one. |
| * |
| * @param scroller |
| * The scroller that should be used for vertical scrolling. |
| */ |
| public void setVerticalScroller(IScroller<?> scroller) { |
| this.verticalScroller = scroller; |
| // ensure to dispose and remove the already registered listener |
| if (this.vBarListener != null) { |
| this.vBarListener.dispose(); |
| this.vBarListener = null; |
| } |
| } |
| |
| public int getMaxWidth() { |
| if (getMaxColumnPosition() < 0) { |
| return -1; |
| } else { |
| int maxWidth = 0; |
| for (int i = 0; i < getMaxColumnPosition(); i++) { |
| maxWidth += this.scrollableLayer.getColumnWidthByPosition(i); |
| } |
| return maxWidth; |
| } |
| } |
| |
| public int getMinVerticalStart() { |
| if (getMinColumnPosition() < 0) { |
| return -1; |
| } else { |
| int minStart = 0; |
| for (int i = 0; i < getMinColumnPosition(); i++) { |
| minStart += this.scrollableLayer.getColumnWidthByPosition(i); |
| } |
| return minStart; |
| } |
| } |
| |
| public int getMaxHeight() { |
| if (getMaxRowPosition() < 0) { |
| return -1; |
| } else { |
| int maxHeight = 0; |
| for (int i = 0; i < getMaxRowPosition(); i++) { |
| maxHeight += getRowHeightByPosition(i); |
| } |
| return maxHeight; |
| } |
| } |
| |
| public int getMinHorizontalStart() { |
| if (getMinRowPosition() < 0) { |
| return -1; |
| } else { |
| int minStart = 0; |
| for (int i = 0; i < getMinRowPosition(); i++) { |
| minStart += getRowHeightByPosition(i); |
| } |
| return minStart; |
| } |
| } |
| |
| // Minimum Origin |
| |
| /** |
| * @return The minimum origin pixel position. |
| */ |
| public PixelCoordinate getMinimumOrigin() { |
| return this.minimumOrigin; |
| } |
| |
| /** |
| * @return The minimum origin column position |
| */ |
| public int getMinimumOriginColumnPosition() { |
| return this.minimumOriginColumnPosition; |
| } |
| |
| /** |
| * @return The minimum origin row position |
| */ |
| public int getMinimumOriginRowPosition() { |
| return this.minimumOriginRowPosition; |
| } |
| |
| /** |
| * Set the minimum origin X pixel position. |
| * |
| * @param newMinimumOriginX |
| * The new minimum origin x. |
| */ |
| public void setMinimumOriginX(int newMinimumOriginX) { |
| if (newMinimumOriginX >= 0) { |
| |
| int minStart = getMinVerticalStart(); |
| if (newMinimumOriginX < minStart) { |
| newMinimumOriginX = minStart; |
| } |
| |
| PixelCoordinate previousMinimumOrigin = this.minimumOrigin; |
| |
| if (newMinimumOriginX != this.minimumOrigin.getX()) { |
| this.minimumOrigin = new PixelCoordinate(newMinimumOriginX, this.minimumOrigin.getY()); |
| int minimumColumn = this.scrollableLayer.getColumnPositionByX(this.minimumOrigin.getX()); |
| |
| // special handling for column resizing to 0 |
| // used e.g. with the ResizeColumnHideShowLayer in |
| // combination with percentage sizing |
| if (minimumColumn < 0 && newMinimumOriginX == this.scrollableLayer.getWidth()) { |
| int left = this.scrollableLayer.getColumnPositionByX(this.minimumOrigin.getX() - 1); |
| // if there are only 0 sized columns between the calculated |
| // left and the current minimum origin column there is no |
| // need to update the minimum column as we have a column |
| // resize to 0 |
| boolean onlyZeroSized = left < this.scrollableLayer.getColumnCount() - 1; |
| for (int i = left + 1; i < this.minimumOriginColumnPosition; i++) { |
| if (this.scrollableLayer.getColumnWidthByPosition(i) > 0) { |
| onlyZeroSized = false; |
| } |
| } |
| if (onlyZeroSized) { |
| minimumColumn = this.minimumOriginColumnPosition; |
| } |
| } |
| |
| int start = this.scrollableLayer.getStartXOfColumnPosition(minimumColumn); |
| while ((minimumColumn > this.minimumOriginColumnPosition) |
| && (this.scrollableLayer.getStartXOfColumnPosition(minimumColumn - 1) == start)) { |
| minimumColumn--; |
| } |
| this.minimumOriginColumnPosition = minimumColumn; |
| } |
| |
| int delta = this.minimumOrigin.getX() - previousMinimumOrigin.getX(); |
| setOriginX(this.origin.getX() + delta); |
| |
| recalculateHorizontalScrollBar(); |
| } |
| } |
| |
| /** |
| * Set the minimum origin Y pixel position. |
| * |
| * @param newMinimumOriginY |
| * The new minimum origin y. |
| */ |
| public void setMinimumOriginY(int newMinimumOriginY) { |
| if (newMinimumOriginY >= 0) { |
| |
| int minStart = getMinHorizontalStart(); |
| if (newMinimumOriginY < minStart) { |
| newMinimumOriginY = minStart; |
| } |
| |
| PixelCoordinate previousMinimumOrigin = this.minimumOrigin; |
| |
| if (newMinimumOriginY != this.minimumOrigin.getY()) { |
| this.minimumOrigin = new PixelCoordinate(this.minimumOrigin.getX(), newMinimumOriginY); |
| this.minimumOriginRowPosition = this.scrollableLayer.getRowPositionByY(this.minimumOrigin.getY()); |
| } |
| |
| int delta = this.minimumOrigin.getY() - previousMinimumOrigin.getY(); |
| setOriginY(this.origin.getY() + delta); |
| |
| recalculateVerticalScrollBar(); |
| } |
| } |
| |
| /** |
| * Set the minimum origin pixel position to the given values. |
| * |
| * @param newMinimumOriginX |
| * The new minimum origin x. |
| * @param newMinimumOriginY |
| * The new minimum origin y. |
| */ |
| public void setMinimumOrigin(int newMinimumOriginX, int newMinimumOriginY) { |
| setMinimumOriginX(newMinimumOriginX); |
| setMinimumOriginY(newMinimumOriginY); |
| } |
| |
| // Origin |
| |
| /** |
| * @return The origin pixel position |
| */ |
| public PixelCoordinate getOrigin() { |
| return this.viewportOff ? this.minimumOrigin : this.origin; |
| } |
| |
| /** |
| * @return The origin column position |
| */ |
| private int getOriginColumnPosition() { |
| // special handling for column resizing to 0 |
| // used e.g. with the ResizeColumnHideShowLayer in combination |
| // with percentage sizing |
| int originColumnPosition = this.scrollableLayer.getColumnPositionByX(getOrigin().getX()); |
| int startX = this.scrollableLayer.getStartXOfColumnPosition(originColumnPosition); |
| while (originColumnPosition > this.minimumOriginColumnPosition |
| && this.scrollableLayer.getStartXOfColumnPosition(originColumnPosition - 1) == startX) { |
| originColumnPosition--; |
| } |
| |
| return originColumnPosition; |
| } |
| |
| /** |
| * @return The origin row position |
| */ |
| private int getOriginRowPosition() { |
| return this.scrollableLayer.getRowPositionByY(getOrigin().getY()); |
| } |
| |
| /** |
| * Range checking for origin X pixel position. |
| * |
| * @param x |
| * The x value to check. |
| * @return A valid x value within bounds: minimum origin x < x < max x (= |
| * column 0 x + width) |
| */ |
| private int boundsCheckOriginX(int x) { |
| int min = this.minimumOrigin.getX(); |
| if (x <= min) { |
| return min; |
| } |
| int max = Math.max(getUnderlyingLayer().getStartXOfColumnPosition(0) + getUnderlyingLayer().getWidth(), min); |
| if (x > max) { |
| return max; |
| } |
| return x; |
| } |
| |
| /** |
| * Range checking for origin Y pixel position. |
| * |
| * @param y |
| * The y value to check. |
| * @return A valid y value within bounds: minimum origin y < y < max y (= |
| * row 0 y + height) |
| */ |
| private int boundsCheckOriginY(int y) { |
| int min = this.minimumOrigin.getY(); |
| if (y <= min) { |
| return min; |
| } |
| int max = Math.max(getUnderlyingLayer().getStartYOfRowPosition(0) + getUnderlyingLayer().getHeight(), min); |
| if (y > max) { |
| return max; |
| } |
| return y; |
| } |
| |
| /** |
| * Set the origin X pixel position. |
| * |
| * @param newOriginX |
| * The new origin x value. |
| */ |
| public void setOriginX(int newOriginX) { |
| newOriginX = boundsCheckOriginX(newOriginX); |
| newOriginX = boundsCheckOriginX(adjustOriginX(newOriginX)); |
| |
| if (newOriginX != this.origin.getX()) { |
| invalidateHorizontalStructure(); |
| this.origin = new PixelCoordinate(newOriginX, this.origin.getY()); |
| fireScrollEvent(); |
| } |
| } |
| |
| /** |
| * Set the origin Y pixel position. |
| * |
| * @param newOriginY |
| * The new origin y value. |
| */ |
| public void setOriginY(int newOriginY) { |
| newOriginY = boundsCheckOriginY(newOriginY); |
| newOriginY = boundsCheckOriginY(adjustOriginY(newOriginY)); |
| |
| if (newOriginY != this.origin.getY()) { |
| invalidateVerticalStructure(); |
| this.origin = new PixelCoordinate(this.origin.getX(), newOriginY); |
| fireScrollEvent(); |
| } |
| } |
| |
| /** |
| * Reset the origin pixel position to the given values. |
| * |
| * @param newOriginX |
| * The new origin x value. |
| * @param newOriginY |
| * The new origin y value. |
| */ |
| public void resetOrigin(int newOriginX, int newOriginY) { |
| PixelCoordinate previousOrigin = this.origin; |
| |
| this.minimumOrigin = new PixelCoordinate(0, 0); |
| this.minimumOriginColumnPosition = 0; |
| this.minimumOriginRowPosition = 0; |
| this.origin = new PixelCoordinate(newOriginX, newOriginY); |
| |
| if (this.origin.getX() != previousOrigin.getX()) { |
| invalidateHorizontalStructure(); |
| } |
| |
| if (this.origin.getY() != previousOrigin.getY()) { |
| invalidateVerticalStructure(); |
| } |
| } |
| |
| // Split viewport support |
| |
| /** |
| * @return The maximum column position of a split viewport or -1 in case |
| * there are no multiple viewports configured. |
| */ |
| public int getMaxColumnPosition() { |
| return this.maxColumnPosition; |
| } |
| |
| /** |
| * @param maxColumnPosition |
| * The right most column position in case split viewports need to |
| * be configured. |
| */ |
| public void setMaxColumnPosition(int maxColumnPosition) { |
| this.maxColumnPosition = maxColumnPosition; |
| } |
| |
| /** |
| * @return The minimum column position of a split viewport or -1 in case |
| * there are no multiple viewports configured. |
| */ |
| public int getMinColumnPosition() { |
| return this.minColumnPosition; |
| } |
| |
| /** |
| * Sets the minimum column position for a split viewport and directly sets |
| * the minimum origin x value dependent on the configuration. |
| * |
| * @param minColumnPosition |
| * The left most column position in case split viewport need to |
| * be configured. |
| */ |
| public void setMinColumnPosition(int minColumnPosition) { |
| this.minColumnPosition = minColumnPosition; |
| // set the minimum origin x dependent to the min column position |
| int newMinOriginX = this.scrollableLayer.getStartXOfColumnPosition(this.minColumnPosition); |
| setMinimumOriginX(newMinOriginX); |
| } |
| |
| /** |
| * @return The maximum row position of a split viewport or -1 in case there |
| * are no multiple viewports configured. |
| */ |
| public int getMaxRowPosition() { |
| return this.maxRowPosition; |
| } |
| |
| /** |
| * @param maxRowPosition |
| * The right most row position in case split viewports need to be |
| * configured. |
| */ |
| public void setMaxRowPosition(int maxRowPosition) { |
| this.maxRowPosition = maxRowPosition; |
| } |
| |
| /** |
| * @return The minimum row position of a split viewport or -1 in case there |
| * are no multiple viewports configured. |
| */ |
| public int getMinRowPosition() { |
| return this.minRowPosition; |
| } |
| |
| /** |
| * Sets the minimum row position for a split viewport and directly sets the |
| * minimum origin y value dependent on the configuration. |
| * |
| * @param minRowPosition |
| * The left most row position in case split viewport need to be |
| * configured. |
| */ |
| public void setMinRowPosition(int minRowPosition) { |
| this.minRowPosition = minRowPosition; |
| // set the minimum origin y dependent to the min row position |
| int newMinOriginY = this.scrollableLayer.getStartYOfRowPosition(this.minRowPosition); |
| setMinimumOriginY(newMinOriginY); |
| } |
| |
| // Configuration |
| |
| @Override |
| protected void registerCommandHandlers() { |
| registerCommandHandler(new RecalculateScrollBarsCommandHandler(this)); |
| registerCommandHandler(new ScrollSelectionCommandHandler(this)); |
| registerCommandHandler(new ShowCellInViewportCommandHandler(this)); |
| registerCommandHandler(new ShowColumnInViewportCommandHandler(this)); |
| registerCommandHandler(new ShowRowInViewportCommandHandler(this)); |
| registerCommandHandler(new ViewportSelectColumnCommandHandler(this)); |
| registerCommandHandler(new ViewportSelectColumnGroupCommandHandler(this)); |
| registerCommandHandler(new ViewportSelectRowCommandHandler(this)); |
| registerCommandHandler(new ViewportSelectRowGroupCommandHandler(this)); |
| registerCommandHandler(new ViewportDragCommandHandler(this)); |
| } |
| |
| // Horizontal features |
| |
| // Columns |
| |
| /** |
| * @return <i>visible</i> column count Note: This takes care of the frozen |
| * columns |
| */ |
| @Override |
| public int getColumnCount() { |
| if (this.viewportOff) { |
| // in case of split viewports we only return the number of columns |
| // in the split |
| if (getMaxColumnPosition() >= 0) { |
| return getMaxColumnPosition(); |
| } else if (getMinColumnPosition() >= 0) { |
| return Math.max(this.scrollableLayer.getColumnCount() - getMinColumnPosition(), 0); |
| } |
| |
| return Math.max(this.scrollableLayer.getColumnCount() - getMinimumOriginColumnPosition(), 0); |
| } else { |
| if (this.cachedColumnCount < 0) { |
| int availableWidth = getClientAreaWidth(); |
| if (availableWidth >= 0) { |
| // lower bound check |
| if (this.origin.getX() < this.minimumOrigin.getX()) { |
| this.origin = new PixelCoordinate(this.minimumOrigin.getX(), this.origin.getY()); |
| } |
| |
| recalculateAvailableWidthAndColumnCount(); |
| } |
| } |
| |
| return this.cachedColumnCount; |
| } |
| } |
| |
| @Override |
| public int getColumnPositionByIndex(int columnIndex) { |
| return this.scrollableLayer.getColumnPositionByIndex(columnIndex) - getOriginColumnPosition(); |
| } |
| |
| @Override |
| public int localToUnderlyingColumnPosition(int localColumnPosition) { |
| |
| int underlyingPosition = getOriginColumnPosition() + localColumnPosition; |
| |
| if (underlyingPosition < getMinimumOriginColumnPosition()) { |
| return -1; |
| } |
| |
| return underlyingPosition; |
| } |
| |
| @Override |
| public int underlyingToLocalColumnPosition(ILayer sourceUnderlyingLayer, int underlyingColumnPosition) { |
| if (sourceUnderlyingLayer != getUnderlyingLayer()) { |
| return -1; |
| } |
| |
| return underlyingColumnPosition - getOriginColumnPosition(); |
| } |
| |
| // Width |
| |
| /** |
| * @return the width of the total number of visible columns |
| */ |
| @Override |
| public int getWidth() { |
| if (this.viewportOff) { |
| int width = this.scrollableLayer.getWidth() - this.scrollableLayer.getStartXOfColumnPosition(getMinimumOriginColumnPosition()); |
| |
| if (getMaxColumnPosition() >= 0) { |
| int maxWidth = getMaxWidth(); |
| if (maxWidth < width) { |
| return maxWidth; |
| } |
| } else { |
| return width; |
| } |
| } |
| if (this.cachedWidth < 0) { |
| recalculateAvailableWidthAndColumnCount(); |
| } |
| return this.cachedWidth; |
| } |
| |
| // Column resize |
| |
| @Override |
| public boolean isColumnPositionResizable(int columnPosition) { |
| return getUnderlyingLayer().isColumnPositionResizable(getOriginColumnPosition() + columnPosition); |
| } |
| |
| // X |
| |
| @Override |
| public int getColumnPositionByX(int x) { |
| return getUnderlyingLayer().getColumnPositionByX(getOrigin().getX() + x) - getOriginColumnPosition(); |
| } |
| |
| @Override |
| public int getStartXOfColumnPosition(int columnPosition) { |
| return getUnderlyingLayer().getStartXOfColumnPosition(getOriginColumnPosition() + columnPosition) - getOrigin().getX(); |
| } |
| |
| // Vertical features |
| |
| // Rows |
| |
| /** |
| * @return total number of rows visible in the viewport |
| */ |
| @Override |
| public int getRowCount() { |
| if (this.viewportOff) { |
| // in case of split viewports we only return the number of rows in |
| // the split |
| if (getMaxRowPosition() >= 0) { |
| return getMaxRowPosition(); |
| } else if (getMinRowPosition() >= 0) { |
| return Math.max(this.scrollableLayer.getRowCount() - getMinRowPosition(), 0); |
| } |
| |
| return Math.max(this.scrollableLayer.getRowCount() - getMinimumOriginRowPosition(), 0); |
| } else { |
| if (this.cachedRowCount < 0) { |
| int availableHeight = getClientAreaHeight(); |
| if (availableHeight >= 0) { |
| |
| // lower bound check |
| if (this.origin.getY() < this.minimumOrigin.getY()) { |
| this.origin = new PixelCoordinate(this.origin.getX(), this.minimumOrigin.getY()); |
| } |
| |
| recalculateAvailableHeightAndRowCount(); |
| } |
| } |
| |
| return this.cachedRowCount; |
| } |
| } |
| |
| @Override |
| public int getRowPositionByIndex(int rowIndex) { |
| return this.scrollableLayer.getRowPositionByIndex(rowIndex) - getOriginRowPosition(); |
| } |
| |
| @Override |
| public int localToUnderlyingRowPosition(int localRowPosition) { |
| |
| int underlyingPosition = getOriginRowPosition() + localRowPosition; |
| |
| if (underlyingPosition < getMinimumOriginRowPosition()) { |
| return -1; |
| } |
| |
| return underlyingPosition; |
| } |
| |
| @Override |
| public int underlyingToLocalRowPosition(ILayer sourceUnderlyingLayer, int underlyingRowPosition) { |
| if (sourceUnderlyingLayer != getUnderlyingLayer()) { |
| return -1; |
| } |
| |
| return underlyingRowPosition - getOriginRowPosition(); |
| } |
| |
| // Height |
| |
| @Override |
| public int getHeight() { |
| if (this.viewportOff) { |
| int height = this.scrollableLayer.getHeight() - this.scrollableLayer.getStartYOfRowPosition(getMinimumOriginRowPosition()); |
| if (getMaxRowPosition() >= 0) { |
| int maxHeight = getMaxHeight(); |
| if (maxHeight < height) { |
| return maxHeight; |
| } |
| } else { |
| return height; |
| } |
| } |
| if (this.cachedHeight < 0) { |
| recalculateAvailableHeightAndRowCount(); |
| } |
| return this.cachedHeight; |
| } |
| |
| // Row resize |
| |
| // Y |
| |
| @Override |
| public int getRowPositionByY(int y) { |
| return getUnderlyingLayer().getRowPositionByY(getOrigin().getY() + y) - getOriginRowPosition(); |
| } |
| |
| @Override |
| public int getStartYOfRowPosition(int rowPosition) { |
| return getUnderlyingLayer().getStartYOfRowPosition(getOriginRowPosition() + rowPosition) - getOrigin().getY(); |
| } |
| |
| // Cell features |
| |
| @Override |
| public Rectangle getBoundsByPosition(int columnPosition, int rowPosition) { |
| int underlyingColumnPosition = localToUnderlyingColumnPosition(columnPosition); |
| int underlyingRowPosition = localToUnderlyingRowPosition(rowPosition); |
| Rectangle bounds = getUnderlyingLayer().getBoundsByPosition(underlyingColumnPosition, underlyingRowPosition); |
| bounds.x -= getOrigin().getX(); |
| bounds.y -= getOrigin().getY(); |
| return bounds; |
| } |
| |
| /** |
| * Clear horizontal caches |
| */ |
| public void invalidateHorizontalStructure() { |
| this.cachedColumnCount = -1; |
| this.cachedClientAreaWidth = 0; |
| this.cachedWidth = -1; |
| } |
| |
| /** |
| * Clear vertical caches |
| */ |
| public void invalidateVerticalStructure() { |
| this.cachedRowCount = -1; |
| this.cachedClientAreaHeight = 0; |
| this.cachedHeight = -1; |
| } |
| |
| /** |
| * Recalculate horizontal dimension properties. |
| */ |
| protected void recalculateAvailableWidthAndColumnCount() { |
| int clientAreaWidth = getMaxColumnPosition() >= 0 ? Math.min(getMaxWidth(), getClientAreaWidth()) : getClientAreaWidth(); |
| int availableWidth = clientAreaWidth; |
| int originColumnPosition = getOriginColumnPosition(); |
| if (originColumnPosition >= 0) { |
| availableWidth += getOrigin().getX() - getUnderlyingLayer().getStartXOfColumnPosition(originColumnPosition); |
| } |
| |
| int maxColumnCount = getMaxColumnPosition() < 0 ? getUnderlyingLayer().getColumnCount() : getMaxColumnPosition(); |
| |
| this.cachedWidth = 0; |
| this.cachedColumnCount = 0; |
| |
| for (int columnPosition = originColumnPosition; columnPosition >= 0 |
| && columnPosition < maxColumnCount && availableWidth > 0; columnPosition++) { |
| |
| int width = getUnderlyingLayer().getColumnWidthByPosition(columnPosition); |
| availableWidth -= width; |
| this.cachedWidth += width; |
| this.cachedColumnCount++; |
| } |
| |
| if (this.cachedColumnCount == maxColumnCount |
| && this.cachedWidth != getUnderlyingLayer().getWidth()) { |
| this.cachedWidth = getUnderlyingLayer().getWidth(); |
| } |
| |
| if (this.cachedWidth > clientAreaWidth) { |
| this.cachedWidth = clientAreaWidth; |
| } |
| |
| int checkedOriginX = boundsCheckOriginX(this.origin.getX()); |
| if (checkedOriginX != this.origin.getX()) { |
| this.origin = new PixelCoordinate(checkedOriginX, this.origin.getY()); |
| } |
| } |
| |
| /** |
| * Recalculate vertical dimension properties. |
| */ |
| protected void recalculateAvailableHeightAndRowCount() { |
| int clientAreaHeight = getMaxRowPosition() >= 0 ? Math.min(getMaxHeight(), getClientAreaHeight()) : getClientAreaHeight(); |
| int availableHeight = clientAreaHeight; |
| int originRowPosition = getOriginRowPosition(); |
| if (originRowPosition >= 0) { |
| availableHeight += getOrigin().getY() - getUnderlyingLayer().getStartYOfRowPosition(originRowPosition); |
| } |
| |
| int maxRowCount = getMaxRowPosition() < 0 ? getUnderlyingLayer().getRowCount() : getMaxRowPosition(); |
| |
| this.cachedHeight = 0; |
| this.cachedRowCount = 0; |
| |
| for (int rowPosition = originRowPosition; rowPosition >= 0 |
| && rowPosition < maxRowCount && availableHeight > 0; rowPosition++) { |
| int height = getUnderlyingLayer().getRowHeightByPosition(rowPosition); |
| availableHeight -= height; |
| this.cachedHeight += height; |
| this.cachedRowCount++; |
| } |
| |
| if (this.cachedRowCount == maxRowCount |
| && this.cachedHeight != getUnderlyingLayer().getHeight()) { |
| this.cachedHeight = getUnderlyingLayer().getHeight(); |
| } |
| |
| if (this.cachedHeight > clientAreaHeight) |
| this.cachedHeight = clientAreaHeight; |
| |
| int checkedOriginY = boundsCheckOriginY(this.origin.getY()); |
| if (checkedOriginY != this.origin.getY()) { |
| this.origin = new PixelCoordinate(this.origin.getX(), checkedOriginY); |
| } |
| |
| if (this.keepInViewportRowPosition > -1) { |
| int idx = getUnderlyingLayer().getRowIndexByPosition(this.keepInViewportRowPosition); |
| int pos = getRowPositionByIndex(idx); |
| if (pos > -1) { |
| moveRowPositionIntoViewport(this.keepInViewportRowPosition); |
| } else { |
| this.keepInViewportRowPosition = -1; |
| } |
| } |
| } |
| |
| /** |
| * Scrolls the table so that the specified cell is visible i.e. in the |
| * Viewport |
| * |
| * @param scrollableColumnPosition |
| * The column position to scroll to. |
| * @param scrollableRowPosition |
| * The row position to scroll to. |
| */ |
| public void moveCellPositionIntoViewport(int scrollableColumnPosition, int scrollableRowPosition) { |
| moveColumnPositionIntoViewport(scrollableColumnPosition); |
| moveRowPositionIntoViewport(scrollableRowPosition); |
| } |
| |
| /** |
| * Scrolls the viewport (if required) so that the specified column is |
| * visible. |
| * |
| * @param scrollableColumnPosition |
| * column position in terms of the Scrollable Layer |
| */ |
| public void moveColumnPositionIntoViewport(int scrollableColumnPosition) { |
| ILayer underlyingLayer = getUnderlyingLayer(); |
| int maxWidth = getMaxWidth(); |
| if (underlyingLayer.getColumnIndexByPosition(scrollableColumnPosition) >= 0 |
| && (maxWidth < 0 || (maxWidth >= 0 |
| && underlyingLayer.getStartXOfColumnPosition(scrollableColumnPosition) < maxWidth))) { |
| if (scrollableColumnPosition >= getMinimumOriginColumnPosition()) { |
| int originColumnPosition = getOriginColumnPosition(); |
| |
| if (scrollableColumnPosition <= originColumnPosition) { |
| // Move left |
| setOriginX(this.scrollableLayer.getStartXOfColumnPosition(scrollableColumnPosition)); |
| } else { |
| int scrollableColumnStartX = underlyingLayer.getStartXOfColumnPosition(scrollableColumnPosition); |
| int scrollableColumnEndX = scrollableColumnStartX + underlyingLayer.getColumnWidthByPosition(scrollableColumnPosition); |
| int clientAreaWidth = getClientAreaWidth(); |
| int viewportEndX = getOrigin().getX() + clientAreaWidth; |
| |
| int maxX = maxWidth >= 0 ? Math.min(maxWidth, scrollableColumnEndX) : scrollableColumnEndX; |
| |
| if (viewportEndX < maxX) { |
| // Move right |
| setOriginX(Math.min(maxX - clientAreaWidth, maxX)); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param scrollableRowPosition |
| * The row position to scroll to. |
| * @see #moveColumnPositionIntoViewport(int) |
| */ |
| public void moveRowPositionIntoViewport(int scrollableRowPosition) { |
| ILayer underlyingLayer = getUnderlyingLayer(); |
| int maxHeight = getMaxHeight(); |
| if (underlyingLayer.getRowIndexByPosition(scrollableRowPosition) >= 0 |
| && (maxHeight < 0 || (maxHeight >= 0 |
| && underlyingLayer.getStartYOfRowPosition(scrollableRowPosition) < maxHeight))) { |
| if (scrollableRowPosition >= getMinimumOriginRowPosition()) { |
| int originRowPosition = getOriginRowPosition(); |
| |
| boolean startKeepInViewport = false; |
| |
| if (scrollableRowPosition <= originRowPosition) { |
| // Move up |
| int oldOriginY = this.origin.getY(); |
| setOriginY(this.scrollableLayer.getStartYOfRowPosition(scrollableRowPosition)); |
| // only start the keep in viewport task if necessary |
| if (this.origin.getY() != oldOriginY) { |
| startKeepInViewport = true; |
| } |
| } else { |
| int scrollableRowStartY = underlyingLayer.getStartYOfRowPosition(scrollableRowPosition); |
| int scrollableRowEndY = scrollableRowStartY + underlyingLayer.getRowHeightByPosition(scrollableRowPosition); |
| int clientAreaHeight = getClientAreaHeight(); |
| int viewportEndY = getOrigin().getY() + clientAreaHeight; |
| |
| int maxY = maxHeight >= 0 ? Math.min(maxHeight, scrollableRowEndY) : scrollableRowEndY; |
| |
| if (viewportEndY < maxY) { |
| // Move down |
| setOriginY(Math.min(maxY - clientAreaHeight, maxY)); |
| startKeepInViewport = true; |
| } |
| } |
| |
| // remember the row position to keep in the viewport to ensure |
| // that the the selection is kept in the viewport |
| // this is necessary for keeping the cell in the viewport if |
| // automatically resize events are generated (see Bug 411670) |
| if (startKeepInViewport) { |
| setKeepInViewportRowPosition(scrollableRowPosition); |
| } |
| } |
| } |
| } |
| |
| protected void fireScrollEvent() { |
| fireLayerEvent(new ScrollEvent(this)); |
| } |
| |
| boolean processingClientAreaResizeCommand = false; |
| |
| @Override |
| public boolean doCommand(ILayerCommand command) { |
| if (command instanceof ClientAreaResizeCommand |
| && command.convertToTargetLayer(this)) { |
| if (this.processingClientAreaResizeCommand) { |
| return false; |
| } |
| |
| this.processingClientAreaResizeCommand = true; |
| |
| // on client area resize we reset the keep in viewport row position |
| this.keepInViewportRowPosition = -1; |
| |
| ClientAreaResizeCommand clientAreaResizeCommand = (ClientAreaResizeCommand) command; |
| |
| // remember the difference from client area to body region area |
| // needed because the scrollbar will be removed and therefore the |
| // client area will become bigger |
| Scrollable scrollable = clientAreaResizeCommand.getScrollable(); |
| Rectangle clientArea = scrollable.getClientArea(); |
| Rectangle calcArea = clientAreaResizeCommand.getCalcArea(); |
| int widthDiff = clientArea.width - calcArea.width; |
| int heightDiff = clientArea.height - calcArea.height; |
| |
| boolean initialClientAreaResize = false; |
| if (this.hBarListener == null && this.horizontalScrollbarEnabled) { |
| initialClientAreaResize = true; |
| |
| ScrollBar hBar = scrollable.getHorizontalBar(); |
| |
| if (hBar != null) { |
| if (this.horizontalScroller != null) { |
| hBar.setEnabled(false); |
| hBar.setVisible(false); |
| } else { |
| this.horizontalScroller = new ScrollBarScroller(hBar); |
| } |
| |
| this.hBarListener = new HorizontalScrollBarHandler(this, this.horizontalScroller); |
| |
| if (scrollable instanceof NatTable) { |
| this.hBarListener.setTable((NatTable) scrollable); |
| } |
| } |
| } |
| |
| if (this.vBarListener == null && this.verticalScrollbarEnabled) { |
| initialClientAreaResize = true; |
| |
| ScrollBar vBar = scrollable.getVerticalBar(); |
| |
| if (vBar != null) { |
| if (this.verticalScroller != null) { |
| vBar.setEnabled(false); |
| vBar.setVisible(false); |
| } else { |
| this.verticalScroller = new ScrollBarScroller(vBar); |
| } |
| |
| this.vBarListener = new VerticalScrollBarHandler(this, this.verticalScroller); |
| |
| if (scrollable instanceof NatTable) { |
| this.vBarListener.setTable((NatTable) scrollable); |
| } |
| } |
| } |
| |
| if (initialClientAreaResize) { |
| handleGridResize(); |
| |
| // after handling the scrollbars recalculate the area to use for |
| // percentage calculation |
| Rectangle possibleArea = scrollable.getClientArea(); |
| possibleArea.width = possibleArea.width - widthDiff; |
| possibleArea.height = possibleArea.height - heightDiff; |
| clientAreaResizeCommand.setCalcArea(possibleArea); |
| } |
| |
| // we don't return true here because the ClientAreaResizeCommand |
| // needs to be handled by the DataLayer in case percentage sizing is |
| // enabled. if we would return true, the DataLayer wouldn't be able |
| // to calculate the column/row sizes regarding the client area |
| boolean result = super.doCommand(command); |
| |
| if (!initialClientAreaResize) { |
| handleGridResize(); |
| } |
| |
| // we need to first give underlying layers the chance to process the |
| // command and afterwards set the processing flag to false |
| // this way we avoid processing the resize multiple times because of |
| // re-calculation in conjunction with scrollbar visibility state |
| // changes |
| this.processingClientAreaResizeCommand = false; |
| |
| return result; |
| } else if (command instanceof TurnViewportOffCommand) { |
| this.savedOrigin = this.origin; |
| this.viewportOff = true; |
| return true; |
| } else if (command instanceof TurnViewportOnCommand) { |
| this.viewportOff = false; |
| this.origin = this.savedOrigin; |
| // only necessary in case of split viewports and auto resizing, but |
| // shouldn't hurt in other cases |
| recalculateScrollBars(); |
| return true; |
| } else if (command instanceof PrintEntireGridCommand) { |
| moveCellPositionIntoViewport(0, 0); |
| } else if (command instanceof ConfigureScalingCommand) { |
| invalidateHorizontalStructure(); |
| invalidateVerticalStructure(); |
| |
| int originDpiX = 0; |
| int originDpiY = 0; |
| int minimumDpiX = 0; |
| int minimumDpiY = 0; |
| int savedDpiX = 0; |
| int savedDpiY = 0; |
| if (this.horizontalDpiConverter != null) { |
| originDpiX = this.horizontalDpiConverter.convertDpiToPixel(this.origin.getX()); |
| savedDpiX = this.horizontalDpiConverter.convertDpiToPixel(this.savedOrigin.getX()); |
| |
| if (this.minimumOrigin.getX() > 0) { |
| minimumDpiX = this.horizontalDpiConverter.convertDpiToPixel(this.minimumOrigin.getX()); |
| } |
| } |
| |
| if (this.verticalDpiConverter != null) { |
| originDpiY = this.verticalDpiConverter.convertDpiToPixel(this.origin.getY()); |
| savedDpiY = this.verticalDpiConverter.convertDpiToPixel(this.savedOrigin.getY()); |
| |
| if (this.minimumOrigin.getY() > 0) { |
| minimumDpiY = this.verticalDpiConverter.convertDpiToPixel(this.minimumOrigin.getY()); |
| } |
| } |
| |
| this.horizontalDpiConverter = ((ConfigureScalingCommand) command).getHorizontalDpiConverter(); |
| this.verticalDpiConverter = ((ConfigureScalingCommand) command).getVerticalDpiConverter(); |
| |
| this.origin = new PixelCoordinate( |
| this.horizontalDpiConverter.convertPixelToDpi(originDpiX), |
| this.verticalDpiConverter.convertPixelToDpi(originDpiY)); |
| this.savedOrigin = new PixelCoordinate( |
| this.horizontalDpiConverter.convertPixelToDpi(savedDpiX), |
| this.verticalDpiConverter.convertPixelToDpi(savedDpiY)); |
| |
| if (minimumDpiX > 0 || minimumDpiY > 0) { |
| this.minimumOrigin = new PixelCoordinate( |
| this.horizontalDpiConverter.convertPixelToDpi(minimumDpiX), |
| this.verticalDpiConverter.convertPixelToDpi(minimumDpiY)); |
| } |
| } |
| return super.doCommand(command); |
| } |
| |
| /** |
| * Recalculate horizontal scrollbar characteristics. |
| */ |
| private void recalculateHorizontalScrollBar() { |
| if (this.hBarListener != null) { |
| this.hBarListener.recalculateScrollBarSize(); |
| |
| if (!this.hBarListener.scroller.isDisposed() |
| && !this.hBarListener.scroller.getEnabled()) { |
| setOriginX(this.minimumOrigin.getX()); |
| } else { |
| setOriginX(this.origin.getX()); |
| } |
| } |
| } |
| |
| /** |
| * Recalculate vertical scrollbar characteristics; |
| */ |
| private void recalculateVerticalScrollBar() { |
| if (this.vBarListener != null) { |
| this.vBarListener.recalculateScrollBarSize(); |
| |
| if (!this.vBarListener.scroller.isDisposed() |
| && !this.vBarListener.scroller.getEnabled()) { |
| setOriginY(this.minimumOrigin.getY()); |
| } else { |
| setOriginY(this.origin.getY()); |
| } |
| } |
| } |
| |
| /** |
| * Recalculate scrollbar characteristics. |
| */ |
| public void recalculateScrollBars() { |
| recalculateHorizontalScrollBar(); |
| recalculateVerticalScrollBar(); |
| } |
| |
| /** |
| * Recalculate viewport characteristics when the grid has been resized. |
| */ |
| protected void handleGridResize() { |
| setOriginX(this.origin.getX()); |
| recalculateHorizontalScrollBar(); |
| setOriginY(this.origin.getY()); |
| recalculateVerticalScrollBar(); |
| } |
| |
| /** |
| * If the client area size is greater than the content size, move origin to |
| * fill as much content as possible. |
| * |
| * @param originX |
| * The origin x value to adjust if necessary. |
| * @return the adjusted x |
| */ |
| protected int adjustOriginX(int originX) { |
| if (getColumnCount() == 0) { |
| return 0; |
| } |
| |
| int availableWidth = getClientAreaWidth() |
| - (this.scrollableLayer.getWidth() - originX); |
| if (availableWidth <= 0) { |
| // in case there is a maximum number of columns configured for |
| // multiple viewports we need to ensure that there is no gap |
| int clientAreaWidth = getClientAreaWidth(); |
| |
| if (getMaxColumnPosition() >= 0 && clientAreaWidth >= getWidth()) { |
| int visibleWidth = calculateVisibleWidth(originX); |
| if (visibleWidth < clientAreaWidth) { |
| originX -= clientAreaWidth - visibleWidth; |
| } |
| } |
| |
| return originX; |
| } else { |
| return boundsCheckOriginX(originX - availableWidth); |
| } |
| } |
| |
| /** |
| * This method will be called in case of split viewports. It is used to |
| * calculate the width of the visible columns, taking into account the |
| * origin and a possible not completely rendered column. The result will be |
| * interpreted by adjusting the originX in case there is less visible |
| * rendering for the set origin compared to the client area width. In this |
| * case the originX needs to be adjusted to fill a gap that would exist |
| * otherwise. |
| * |
| * @param originX |
| * The originX that is currently set. |
| * @return The width of the visible columns for the current set origin. |
| */ |
| private int calculateVisibleWidth(int originX) { |
| int partialVisibleColumnWidth = getUnderlyingLayer().getStartXOfColumnPosition(getOriginColumnPosition() + 1) - originX; |
| int visibleWidth = partialVisibleColumnWidth; |
| for (int i = getOriginColumnPosition() + 1; i < getMaxColumnPosition(); i++) { |
| visibleWidth += getUnderlyingLayer().getColumnWidthByPosition(i); |
| } |
| return visibleWidth; |
| } |
| |
| /** |
| * If the client area size is greater than the content size, move origin to |
| * fill as much content as possible. |
| * |
| * @param originY |
| * The origin y value to adjust if necessary. |
| * @return the adjusted y |
| */ |
| protected int adjustOriginY(int originY) { |
| if (getRowCount() == 0) { |
| return 0; |
| } |
| |
| int availableHeight = getClientAreaHeight() - (this.scrollableLayer.getHeight() - originY); |
| |
| if (availableHeight <= 0) { |
| // in case there is a maximum number of rows configured for multiple |
| // viewports |
| // we need to ensure that there is no gap |
| int clientAreaHeight = getClientAreaHeight(); |
| if (getMaxRowPosition() >= 0 && clientAreaHeight >= getHeight()) { |
| int visibleHeight = calculateVisibleHeight(originY); |
| if (visibleHeight < clientAreaHeight) { |
| originY -= clientAreaHeight - visibleHeight; |
| } |
| } |
| |
| return originY; |
| } else { |
| return boundsCheckOriginY(originY - availableHeight); |
| } |
| } |
| |
| /** |
| * This method will be called in case of split viewports. It is used to |
| * calculate the height of the visible rows, taking into account the origin |
| * and a possible not completely rendered row. The result will be |
| * interpreted by adjusting the originY in case there is less visible |
| * rendering for the set origin compared to the client area height. In this |
| * case the originY needs to be adjusted to fill a gap that would exist |
| * otherwise. |
| * |
| * @param originY |
| * The originY that is currently set. |
| * @return The height of the visible rows for the current set origin. |
| */ |
| private int calculateVisibleHeight(int originY) { |
| int partialVisibleRowHeight = getUnderlyingLayer().getStartYOfRowPosition(getOriginRowPosition() + 1) - originY; |
| int visibleHeight = partialVisibleRowHeight; |
| for (int i = getOriginRowPosition() + 1; i < getMaxRowPosition(); i++) { |
| visibleHeight += getUnderlyingLayer().getRowHeightByPosition(i); |
| } |
| return visibleHeight; |
| } |
| |
| /** |
| * Scrolls the viewport vertically by a page. This is done by creating a |
| * MoveSelectionCommand to move the selection, which will then trigger an |
| * update of the viewport. |
| * |
| * @param scrollSelectionCommand |
| * The {@link ScrollSelectionCommand} that is transfered to a |
| * {@link MoveSelectionCommand} |
| */ |
| public void scrollVerticallyByAPage(ScrollSelectionCommand scrollSelectionCommand) { |
| getUnderlyingLayer().doCommand(scrollVerticallyByAPageCommand(scrollSelectionCommand)); |
| } |
| |
| protected MoveSelectionCommand scrollVerticallyByAPageCommand(ScrollSelectionCommand scrollSelectionCommand) { |
| return new MoveSelectionCommand( |
| scrollSelectionCommand.getDirection(), |
| getRowCount(), |
| scrollSelectionCommand.isShiftMask(), |
| scrollSelectionCommand.isControlMask()); |
| } |
| |
| /** |
| * @return <code>true</code> if last column is completely displayed, |
| * <code>false</code> otherwise |
| */ |
| protected boolean isLastColumnCompletelyDisplayed() { |
| int lastDisplayableColumnIndex = getUnderlyingLayer().getColumnIndexByPosition(getUnderlyingLayer().getColumnCount() - 1); |
| int visibleColumnCount = getColumnCount(); |
| int lastVisibleColumnIndex = getColumnIndexByPosition(visibleColumnCount - 1); |
| |
| return (lastVisibleColumnIndex == lastDisplayableColumnIndex) |
| && (getClientAreaWidth() >= getWidth()); |
| } |
| |
| /** |
| * @return <code>true</code> if last row is completely displayed, |
| * <code>false</code> otherwise |
| */ |
| protected boolean isLastRowCompletelyDisplayed() { |
| int lastDisplayableRowIndex = getUnderlyingLayer().getRowIndexByPosition(getUnderlyingLayer().getRowCount() - 1); |
| int visibleRowCount = getRowCount(); |
| int lastVisibleRowIndex = getRowIndexByPosition(visibleRowCount - 1); |
| |
| return (lastVisibleRowIndex == lastDisplayableRowIndex) |
| && (getClientAreaHeight() >= getHeight()); |
| } |
| |
| // Event handling |
| |
| @Override |
| public void handleLayerEvent(ILayerEvent event) { |
| if (event instanceof IStructuralChangeEvent) { |
| IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event; |
| if (structuralChangeEvent.isHorizontalStructureChanged()) { |
| invalidateHorizontalStructure(); |
| |
| // saved origin correction for multi viewports |
| if (this.viewportOff |
| && (getMaxColumnPosition() >= 0 || getMinColumnPosition() >= 0) |
| && event instanceof ColumnResizeEvent) { |
| correctSavedOriginX(); |
| } |
| } |
| if (structuralChangeEvent.isVerticalStructureChanged()) { |
| invalidateVerticalStructure(); |
| |
| // saved origin correction for multi viewports |
| if (this.viewportOff |
| && (getMaxRowPosition() >= 0 || getMinRowPosition() >= 0) |
| && event instanceof RowResizeEvent) { |
| correctSavedOriginY(); |
| } |
| } |
| } |
| |
| if (event instanceof CellSelectionEvent) { |
| processSelection((CellSelectionEvent) event); |
| } else if (event instanceof ColumnSelectionEvent) { |
| processColumnSelection((ColumnSelectionEvent) event); |
| } else if (event instanceof RowSelectionEvent) { |
| processRowSelection((RowSelectionEvent) event); |
| } |
| |
| super.handleLayerEvent(event); |
| } |
| |
| /** |
| * This method gets called in case of automatic column resize is performed |
| * when split viewports are active. |
| * <p> |
| * Automatic resize commands will first turn the viewport off, then perform |
| * the resizing and then turn the viewport on again. Turning the viewport |
| * off and on again causes reapplying the origin, which has impact on split |
| * viewport minimum/maximum origins. |
| */ |
| private void correctSavedOriginX() { |
| int newOriginX = this.savedOrigin.getX(); |
| |
| int columnPosition = 0; |
| if (getMinColumnPosition() >= 0) { |
| int possibleWidth = 0; |
| for (int col = columnPosition; col < getMinColumnPosition(); col++) { |
| possibleWidth += this.scrollableLayer.getColumnWidthByPosition(col); |
| } |
| if (possibleWidth != this.minimumOrigin.getX()) { |
| int delta = this.minimumOrigin.getX() - possibleWidth; |
| newOriginX = newOriginX - delta; |
| // as the width of the other split viewport has changed, we need |
| // to update the minimum width too |
| this.minimumOrigin = new PixelCoordinate(this.minimumOrigin.getX() |
| - delta, this.minimumOrigin.getY()); |
| } |
| } else { |
| int originX = this.savedOrigin.getX(); |
| int visibleWidth = calculateVisibleWidth(originX); |
| int clientAreaWidth = getClientAreaWidth(); |
| if (visibleWidth < clientAreaWidth) { |
| int possibleWidth = 0; |
| int columnCount = getMaxColumnPosition() >= 0 ? getMaxColumnPosition() : this.scrollableLayer.getColumnCount(); |
| for (int col = columnPosition; col < columnCount; col++) { |
| possibleWidth += this.scrollableLayer.getColumnWidthByPosition(col); |
| } |
| if (possibleWidth >= clientAreaWidth) { |
| newOriginX = this.scrollableLayer.getStartXOfColumnPosition(columnPosition); |
| } else { |
| newOriginX = this.scrollableLayer.getWidth() - clientAreaWidth; |
| } |
| newOriginX = Math.max(0, newOriginX); |
| } |
| } |
| this.savedOrigin = new PixelCoordinate(newOriginX, this.savedOrigin.getY()); |
| } |
| |
| /** |
| * This method gets called in case of automatic row resize is performed when |
| * split viewports are active. |
| * <p> |
| * Automatic resize commands will first turn the viewport off, then perform |
| * the resizing and then turn the viewport on again. Turning the viewport |
| * off and on again causes reapplying the origin, which has impact on split |
| * viewport minimum/maximum origins. |
| * </p> |
| */ |
| private void correctSavedOriginY() { |
| int newOriginY = this.savedOrigin.getY(); |
| |
| int rowPosition = 0; |
| if (getMinRowPosition() >= 0) { |
| int possibleHeight = 0; |
| for (int row = rowPosition; row < getMinRowPosition(); row++) { |
| possibleHeight += this.scrollableLayer.getRowHeightByPosition(row); |
| } |
| if (possibleHeight != this.minimumOrigin.getY()) { |
| int delta = this.minimumOrigin.getY() - possibleHeight; |
| newOriginY = newOriginY - delta; |
| // as the height of the other split viewport has changed, we |
| // need to update the minimum height too |
| this.minimumOrigin = new PixelCoordinate(this.minimumOrigin.getX(), this.minimumOrigin.getY() - delta); |
| } |
| } else { |
| int originY = this.savedOrigin.getY(); |
| int visibleHeight = calculateVisibleHeight(originY); |
| int clientAreaHeight = getClientAreaHeight(); |
| if (visibleHeight < clientAreaHeight) { |
| int possibleHeight = 0; |
| int rowCount = getMaxRowPosition() >= 0 ? getMaxRowPosition() : this.scrollableLayer.getRowCount(); |
| for (int row = rowPosition; row < rowCount; row++) { |
| possibleHeight += this.scrollableLayer.getRowHeightByPosition(row); |
| } |
| if (possibleHeight >= clientAreaHeight) { |
| newOriginY = this.scrollableLayer.getStartYOfRowPosition(rowPosition); |
| } else { |
| newOriginY = this.scrollableLayer.getHeight() - clientAreaHeight; |
| } |
| newOriginY = Math.max(0, newOriginY); |
| } |
| } |
| this.savedOrigin = new PixelCoordinate(this.savedOrigin.getX(), newOriginY); |
| } |
| |
| /** |
| * Handle {@link CellSelectionEvent} |
| * |
| * @param selectionEvent |
| * The event to handle |
| */ |
| private void processSelection(CellSelectionEvent selectionEvent) { |
| moveCellPositionIntoViewport( |
| selectionEvent.getColumnPosition(), |
| selectionEvent.getRowPosition()); |
| adjustHorizontalScrollBar(); |
| adjustVerticalScrollBar(); |
| } |
| |
| /** |
| * Handle {@link ColumnSelectionEvent} |
| * |
| * @param selectionEvent |
| * The event to handle |
| */ |
| private void processColumnSelection(ColumnSelectionEvent selectionEvent) { |
| for (Range columnPositionRange : selectionEvent.getColumnPositionRanges()) { |
| moveColumnPositionIntoViewport(columnPositionRange.end - 1); |
| adjustHorizontalScrollBar(); |
| } |
| } |
| |
| /** |
| * Handle {@link RowSelectionEvent} |
| * |
| * @param selectionEvent |
| * The event to handle |
| */ |
| private void processRowSelection(RowSelectionEvent selectionEvent) { |
| int rowPositionToMoveIntoViewport = selectionEvent.getRowPositionToMoveIntoViewport(); |
| if (rowPositionToMoveIntoViewport >= 0) { |
| moveRowPositionIntoViewport(rowPositionToMoveIntoViewport); |
| adjustVerticalScrollBar(); |
| } |
| } |
| |
| /** |
| * Adjusts horizontal scrollbar to sync with current state of viewport. |
| */ |
| private void adjustHorizontalScrollBar() { |
| if (this.hBarListener != null) { |
| this.hBarListener.adjustScrollBar(); |
| } |
| } |
| |
| /** |
| * Adjusts vertical scrollbar to sync with current state of viewport. |
| */ |
| private void adjustVerticalScrollBar() { |
| if (this.vBarListener != null) { |
| this.vBarListener.adjustScrollBar(); |
| } |
| } |
| |
| // Accessors |
| |
| /** |
| * @return The width of the visible client area. Will recalculate horizontal |
| * dimension information if the width has changed. |
| */ |
| public int getClientAreaWidth() { |
| int clientAreaWidth = getClientAreaProvider().getClientArea().width; |
| if (clientAreaWidth != this.cachedClientAreaWidth) { |
| invalidateHorizontalStructure(); |
| this.cachedClientAreaWidth = clientAreaWidth; |
| } |
| return this.cachedClientAreaWidth; |
| } |
| |
| /** |
| * @return The height of the visible client area. Will recalculate vertical |
| * dimension information if the height has changed. |
| */ |
| public int getClientAreaHeight() { |
| int clientAreaHeight = getClientAreaProvider().getClientArea().height; |
| if (clientAreaHeight != this.cachedClientAreaHeight) { |
| invalidateVerticalStructure(); |
| this.cachedClientAreaHeight = clientAreaHeight; |
| } |
| return this.cachedClientAreaHeight; |
| } |
| |
| /** |
| * @return The scrollable layer underlying the viewport. |
| */ |
| public IUniqueIndexLayer getScrollableLayer() { |
| return this.scrollableLayer; |
| } |
| |
| @Override |
| public String toString() { |
| return "Viewport Layer"; //$NON-NLS-1$ |
| } |
| |
| // Edge hover scrolling |
| |
| /** |
| * Used for edge hover scrolling. Called from the |
| * ViewportDragCommandHandler. |
| * |
| * @param x |
| * The x coordinate |
| * @param y |
| * The y coordinate |
| */ |
| public void drag(int x, int y) { |
| if (x < 0 && y < 0) { |
| cancelEdgeHoverScroll(); |
| return; |
| } |
| |
| MoveViewportRunnable move = this.edgeHoverRunnable; |
| if (move == null) { |
| move = new MoveViewportRunnable(); |
| } |
| |
| Rectangle clientArea = getClientAreaProvider().getClientArea(); |
| { |
| int change = 0; |
| int minX = clientArea.x; |
| int maxX = clientArea.x + clientArea.width; |
| if (x >= minX && x < minX + EDGE_HOVER_REGION_SIZE) { |
| change = -1; |
| } else if (x >= maxX - EDGE_HOVER_REGION_SIZE && x < maxX) { |
| change = 1; |
| } |
| move.x = change; |
| } |
| { |
| int change = 0; |
| int minY = clientArea.y; |
| int maxY = clientArea.y + clientArea.height; |
| if (y >= minY && y < minY + EDGE_HOVER_REGION_SIZE) { |
| change = -1; |
| } else if (y >= maxY - EDGE_HOVER_REGION_SIZE && y < maxY) { |
| change = 1; |
| } |
| move.y = change; |
| } |
| |
| if (move.x != 0 || move.y != 0) { |
| move.schedule(); |
| } else { |
| cancelEdgeHoverScroll(); |
| } |
| } |
| |
| /** |
| * Used to scroll in the given direction on drag operations outside the |
| * visible region. Does not start a background thread for automatic |
| * scrolling. |
| * |
| * @param horizontal |
| * The horizontal movement for the scroll operation |
| * <code>MoveDirectionEnum.LEFT</code>, |
| * <code>MoveDirectionEnum.RIGHT</code>, |
| * <code>MoveDirectionEnum.NONE</code> |
| * @param vertical |
| * The vertical movement for the scroll operation |
| * <code>MoveDirectionEnum.UP</code>, |
| * <code>MoveDirectionEnum.DOWN</code>, |
| * <code>MoveDirectionEnum.NONE</code> |
| * @since 1.3 |
| */ |
| public void drag(MoveDirectionEnum horizontal, MoveDirectionEnum vertical) { |
| if ((horizontal == null && vertical == null) |
| || MoveDirectionEnum.NONE.equals(horizontal) && MoveDirectionEnum.NONE.equals(vertical)) { |
| return; |
| } |
| |
| int x = 0; |
| int y = 0; |
| |
| if (horizontal != null) { |
| switch (horizontal) { |
| case LEFT: |
| x = -1; |
| break; |
| case RIGHT: |
| x = 1; |
| break; |
| case NONE: |
| x = 0; |
| } |
| } |
| |
| if (vertical != null) { |
| switch (vertical) { |
| case UP: |
| y = -1; |
| break; |
| case DOWN: |
| y = 1; |
| break; |
| case NONE: |
| y = 0; |
| } |
| } |
| |
| if (x != 0) { |
| setOriginX(getUnderlyingLayer().getStartXOfColumnPosition( |
| getOriginColumnPosition() + x)); |
| } |
| if (y != 0) { |
| setOriginY(getUnderlyingLayer().getStartYOfRowPosition( |
| getOriginRowPosition() + y)); |
| } |
| } |
| |
| /** |
| * Cancels an edge hover scroll. |
| */ |
| private void cancelEdgeHoverScroll() { |
| this.edgeHoverRunnable = null; |
| } |
| |
| /** |
| * Enable/disable the horizontal scrollbar in this ViewportLayer. |
| * <p> |
| * Note: Setting the value to <code>false</code> will avoid registering a |
| * HorizontalScrollBarHandler, which means that there are no actions |
| * performed on the horizontal scrollbar in any case. If a horizontal |
| * scrollbar is rendered, it will be shown disabled. The rendering of |
| * scrollbar is typically configured via style bit in the NatTable control. |
| * So if there is a disabled scrollbar rendered check the style bits of the |
| * NatTable, and try to remove SWT.H_SCROLL which is set in the default |
| * style options. |
| * </p> |
| * |
| * @param enabled |
| * <code>false</code> to disable the horizontal scrollbar, |
| * <code>true</code> to enable it. |
| */ |
| public void setHorizontalScrollbarEnabled(boolean enabled) { |
| this.horizontalScrollbarEnabled = enabled; |
| } |
| |
| /** |
| * Enable/disable the vertical scrollbar in this ViewportLayer. |
| * <p> |
| * Note: Setting the value to <code>false</code> will avoid registering a |
| * VerticalScrollBarHandler which means that there are no actions performed |
| * on the vertical scrollbar in any case. If a vertical scrollbar is |
| * rendered, it will be shown disabled. The rendering of scrollbar is |
| * typically configured via style bit in the NatTable control. So if there |
| * is a disabled scrollbar rendered check the style bits of the NatTable, |
| * and try to remove SWT.V_SCROLL which is set in the default style options. |
| * </p> |
| * |
| * @param enabled |
| * <code>false</code> to disable the vertical scrollbar, |
| * <code>true</code> to enable it. |
| */ |
| public void setVerticalScrollbarEnabled(boolean enabled) { |
| this.verticalScrollbarEnabled = enabled; |
| } |
| |
| /** |
| * @return <code>true</code> because the {@link ViewportLayer} is intended |
| * to be a dynamic size layer. |
| * @since 1.4 |
| */ |
| @Override |
| public boolean isDynamicSizeLayer() { |
| return true; |
| } |
| |
| /** |
| * Set the row position related to the underlying layer that should be kept |
| * visible in the viewport. Mainly used for configurations with dynamic row |
| * heights that are calculated on rendering. If a row should become visible |
| * via {@link #moveCellPositionIntoViewport(int, int)} or |
| * {@link #moveRowPositionIntoViewport(int)}, but the rows above are |
| * resized, the row that should move into the viewport is moved out of it |
| * again. Setting the value here leads to keeping the row inside the |
| * viewport on {@link #recalculateAvailableHeightAndRowCount()}. |
| * <p> |
| * The value will be reset on {@link ClientAreaResizeCommand} handling and |
| * via {@link ScrollBarHandlerTemplate} if a manual scrolling is triggered. |
| * </p> |
| * |
| * @param rowPosition |
| * the row position in the underlying layer of the row that |
| * should be kept inside the viewport, or -1 to reset the keep |
| * row in viewport handling. |
| * |
| * @since 1.6 |
| */ |
| public void setKeepInViewportRowPosition(int rowPosition) { |
| this.keepInViewportRowPosition = rowPosition; |
| } |
| |
| /** |
| * Runnable that incrementally scrolls the viewport when drag hovering over |
| * an edge. |
| */ |
| class MoveViewportRunnable implements Runnable { |
| |
| private int x; |
| private int y; |
| |
| private final Display display = Display.getCurrent(); |
| |
| public void schedule() { |
| if (ViewportLayer.this.edgeHoverRunnable != this) { |
| ViewportLayer.this.edgeHoverRunnable = this; |
| this.display.timerExec(500, this); |
| } |
| } |
| |
| @Override |
| public void run() { |
| if (ViewportLayer.this.edgeHoverRunnable != this) { |
| return; |
| } |
| |
| if (this.x != 0) { |
| setOriginX(getUnderlyingLayer().getStartXOfColumnPosition(getOriginColumnPosition() + this.x)); |
| } |
| if (this.y != 0) { |
| setOriginY(getUnderlyingLayer().getStartYOfRowPosition(getOriginRowPosition() + this.y)); |
| } |
| |
| this.display.timerExec(100, this); |
| } |
| |
| } |
| |
| } |