blob: 60ffb36771694f672b72bc98aa984a2dd5875fa7 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2012, 2021 Original NatTable 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 NatTable authors and others - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ecommons.waltable.layer;
import static org.eclipse.statet.ecommons.waltable.coordinate.Orientation.HORIZONTAL;
import static org.eclipse.statet.ecommons.waltable.coordinate.Orientation.VERTICAL;
import static org.eclipse.statet.ecommons.waltable.painter.cell.GraphicsUtils.safe;
import java.util.Properties;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.statet.ecommons.waltable.command.ILayerCommand;
import org.eclipse.statet.ecommons.waltable.config.ConfigRegistry;
import org.eclipse.statet.ecommons.waltable.config.IConfigRegistry;
import org.eclipse.statet.ecommons.waltable.coordinate.LRectangle;
import org.eclipse.statet.ecommons.waltable.coordinate.Orientation;
import org.eclipse.statet.ecommons.waltable.coordinate.PositionOutOfBoundsException;
import org.eclipse.statet.ecommons.waltable.layer.cell.AggregrateConfigLabelAccumulator;
import org.eclipse.statet.ecommons.waltable.layer.cell.ForwardLayerCell;
import org.eclipse.statet.ecommons.waltable.layer.cell.IConfigLabelAccumulator;
import org.eclipse.statet.ecommons.waltable.layer.cell.ILayerCell;
import org.eclipse.statet.ecommons.waltable.layer.cell.ILayerCellDim;
import org.eclipse.statet.ecommons.waltable.layer.cell.LayerCellDim;
import org.eclipse.statet.ecommons.waltable.painter.layer.ILayerPainter;
import org.eclipse.statet.ecommons.waltable.ui.IClientAreaProvider;
import org.eclipse.statet.ecommons.waltable.ui.binding.UiBindingRegistry;
/**
* A composite layer is a layer that is made up of a number of underlying child layers. This class assumes that the child
* layers are laid out in a regular grid pattern where the child layers in each composite row all have the same number of
* rows and the same height, and the child layers in each composite column each have the same number of columns and
* the same width.
*/
public class CompositeLayer extends AbstractLayer {
protected static final class Child {
public final int layoutX;
public final int layoutY;
public final String label;
public final ILayer layer;
private IConfigLabelAccumulator configLabelAccumulator;
private Child(final int x, final int y, final String label, final ILayer layer) {
this.layoutX= x;
this.layoutY= y;
this.label= label;
this.layer= layer;
}
}
protected final int layoutXCount;
protected final int layoutYCount;
/** Data struct. for child Layers */
private final Child[][] childLayout;
public CompositeLayer(final int layoutXCount, final int layoutYCount) {
this.layoutXCount= layoutXCount;
this.layoutYCount= layoutYCount;
this.childLayout= new Child[layoutXCount][layoutYCount];
initDims();
}
@Override
protected void initDims() {
if (this.childLayout == null) {
return;
}
for (final Orientation orientation : Orientation.values()) {
setDim((ignoreRef(orientation)) ?
new CompositeLayerDim.IgnoreRef(this, orientation) :
new CompositeLayerDim(this, orientation) );
}
}
protected boolean ignoreRef(final Orientation orientation) {
return false;
}
final CompositeLayerDim get(final Orientation orientation) {
return (CompositeLayerDim) getDim(orientation);
}
@Override
protected ILayerPainter createPainter() {
return new CompositeLayerPainter();
}
// Dispose
@Override
public void dispose() {
for (int layoutX= 0; layoutX < this.layoutXCount; layoutX++) {
for (int layoutY= 0; layoutY < this.layoutYCount; layoutY++) {
final Child child= this.childLayout[layoutX][layoutY];
if (child != null) {
child.layer.dispose();
}
}
}
}
// Persistence
@Override
public void saveState(final String prefix, final Properties properties) {
for (int layoutX= 0; layoutX < this.layoutXCount; layoutX++) {
for (int layoutY= 0; layoutY < this.layoutYCount; layoutY++) {
final Child child= this.childLayout[layoutX][layoutY];
if (child != null) {
child.layer.saveState(prefix + "." + child.label, properties); //$NON-NLS-1$
}
}
}
super.saveState(prefix, properties);
}
@Override
public void loadState(final String prefix, final Properties properties) {
for (int layoutX= 0; layoutX < this.layoutXCount; layoutX++) {
for (int layoutY= 0; layoutY < this.layoutYCount; layoutY++) {
final Child child= this.childLayout[layoutX][layoutY];
if (child != null) {
child.layer.loadState(prefix + "." + child.label, properties); //$NON-NLS-1$
}
}
}
super.loadState(prefix, properties);
}
// Configuration
@Override
public void configure(final ConfigRegistry configRegistry, final UiBindingRegistry uiBindingRegistry) {
for (int layoutX= 0; layoutX < this.layoutXCount; layoutX++) {
for (int layoutY= 0; layoutY < this.layoutYCount; layoutY++) {
this.childLayout[layoutX][layoutY].layer.configure(configRegistry, uiBindingRegistry);
}
}
super.configure(configRegistry, uiBindingRegistry);
}
/**
* {@inheritDoc}
* Handle commands
*/
@Override
public boolean doCommand(final ILayerCommand command) {
if (super.doCommand(command)) {
return true;
}
return doCommandOnChildLayers(command);
}
protected boolean doCommandOnChildLayers(final ILayerCommand command) {
for (int layoutX= 0; layoutX < this.layoutXCount; layoutX++) {
for (int layoutY= 0; layoutY < this.layoutYCount; layoutY++) {
if (this.childLayout[layoutX][layoutY].layer.doCommand(command)) {
return true;
}
}
}
return false;
}
// Cell features
@Override
public ILayerCell getCellByPosition(final long columnPosition, final long rowPosition) {
final Child child= getChildByPosition(columnPosition, rowPosition);
if (child == null) {
throw new PositionOutOfBoundsException(columnPosition + ", " + rowPosition); //$NON-NLS-1$
}
final long childColumnPosition= columnPosition - get(HORIZONTAL).getLayoutPosition(child.layoutX);
final long childRowPosition= rowPosition - get(VERTICAL).getLayoutPosition(child.layoutY);
final ILayerCell underlyingCell= child.layer.getCellByPosition(childColumnPosition, childRowPosition);
final ILayerCellDim hDim= transformCellDim(underlyingCell.getDim(HORIZONTAL), columnPosition);
final ILayerCellDim vDim= transformCellDim(underlyingCell.getDim(VERTICAL), rowPosition);
return new ForwardLayerCell(this, hDim, vDim, underlyingCell) {
@Override
public LabelStack getConfigLabels() {
final LabelStack configLabels= super.getConfigLabels();
if (child.configLabelAccumulator != null) {
child.configLabelAccumulator.accumulateConfigLabels(configLabels, childColumnPosition, childRowPosition);
}
configLabels.addLabel(child.label);
return configLabels;
}
};
}
protected ILayerCellDim transformCellDim(final ILayerCellDim underlyingDim, final long position) {
final long originPosition= (underlyingDim.getPosition() == underlyingDim.getOriginPosition()) ?
position :
get(underlyingDim.getOrientation()).underlyingToLocalPosition(
position, underlyingDim.getOriginPosition() );
return new LayerCellDim(underlyingDim.getOrientation(), underlyingDim.getId(),
position, originPosition, underlyingDim.getPositionSpan() );
}
// Child layer stuff
public void setChildLayer(final String label, final ILayer childLayer, final int layoutX, final int layoutY) {
if (childLayer == null) {
throw new IllegalArgumentException("Cannot set null child layer"); //$NON-NLS-1$
}
final Child child= new Child(layoutX, layoutY, label, childLayer);
childLayer.addLayerListener(this);
this.childLayout[layoutX][layoutY]= child;
get(HORIZONTAL).updateChild(layoutX, layoutY, childLayer);
get(VERTICAL).updateChild(layoutY, layoutX, childLayer);
childLayer.setClientAreaProvider(new IClientAreaProvider() {
@Override
public LRectangle getClientArea() {
return getChildClientArea(child);
}
});
}
/**
* Adds the configLabelAccumulator to the existing label accumulators.
*/
public void addConfigLabelAccumulatorForRegion(final String regionLabel, final IConfigLabelAccumulator configLabelAccumulator) {
final Child child= getChildByLabel(regionLabel);
if (child.configLabelAccumulator == null) {
child.configLabelAccumulator= configLabelAccumulator;
}
else {
final AggregrateConfigLabelAccumulator aggregateAccumulator;
if (child.configLabelAccumulator instanceof AggregrateConfigLabelAccumulator) {
aggregateAccumulator= (AggregrateConfigLabelAccumulator) child.configLabelAccumulator;
}
else {
aggregateAccumulator= new AggregrateConfigLabelAccumulator();
aggregateAccumulator.add(child.configLabelAccumulator);
child.configLabelAccumulator= aggregateAccumulator;
}
aggregateAccumulator.add(configLabelAccumulator);
}
}
private LRectangle getChildClientArea(final Child child) {
final LRectangle compositeClientArea= getClientAreaProvider().getClientArea();
final LRectangle childClientArea= new LRectangle(
compositeClientArea.x + get(HORIZONTAL).getLayoutStart(child.layoutX),
compositeClientArea.y + get(VERTICAL).getLayoutStart(child.layoutY),
child.layer.getDim(HORIZONTAL).getPreferredSize(),
child.layer.getDim(VERTICAL).getPreferredSize() );
final LRectangle intersection= compositeClientArea.intersection(childClientArea);
return intersection;
}
/**
* @param layoutX col position in the CompositeLayer
* @param layoutY row position in the CompositeLayer
* @return child layer according to the Composite Layer Layout
*/
public ILayer getChildLayerByLayoutCoordinate(final int layoutX, final int layoutY) {
if (layoutX < 0 || layoutX >= this.layoutXCount || layoutY < 0 || layoutY >= this.layoutYCount) {
return null;
}
else {
return this.childLayout[layoutX][layoutY].layer;
}
}
/**
* @param x pixel position
* @param y pixel position
* @return Region which the given position is in
*/
@Override
public LabelStack getRegionLabelsByXY(final long x, final long y) {
final Child child= getChildByPixelXY(x, y);
if (child == null) {
return null;
}
final long childX= x - get(HORIZONTAL).getLayoutStart(child.layoutX);
final long childY= y - get(VERTICAL).getLayoutStart(child.layoutY);
final LabelStack regionLabels= child.layer.getRegionLabelsByXY(childX, childY);
regionLabels.addLabel(child.label);
return regionLabels;
}
@Override
public ILayer getUnderlyingLayerByPosition(final long columnPosition, final long rowPosition) {
final Child child= getChildByPosition(columnPosition, rowPosition);
return (child != null) ? child.layer : null;
}
// Layout coordinate accessors
protected final Child getChildByLabel(final String label) {
for (int layoutX= 0; layoutX < this.layoutXCount; layoutX++) {
for (int layoutY= 0; layoutY < this.layoutYCount; layoutY++) {
if (label.equals(this.childLayout[layoutX][layoutY].label)) {
return this.childLayout[layoutX][layoutY];
}
}
}
return null;
}
protected final Child getChildByLayer(final ILayer childLayer) {
for (int layoutX= 0; layoutX < this.layoutXCount; layoutX++) {
for (int layoutY= 0; layoutY < this.layoutYCount; layoutY++) {
if (this.childLayout[layoutX][layoutY].layer == childLayer) {
return this.childLayout[layoutX][layoutY];
}
}
}
return null;
}
protected final Child getChildByPixelXY(final long x, final long y) {
final int layoutX= get(HORIZONTAL).getLayoutByPixel(x);
if (layoutX < 0) {
return null;
}
final int layoutY= get(VERTICAL).getLayoutByPixel(y);
if (layoutY < 0) {
return null;
}
return this.childLayout[layoutX][layoutY];
}
protected final Child getChildByPosition(final long compositeColumnPosition, final long compositeRowPosition) {
final int layoutX= get(HORIZONTAL).getLayoutByPosition(compositeColumnPosition);
if (layoutX < 0) {
return null;
}
final int layoutY= get(VERTICAL).getLayoutByPosition(compositeRowPosition);
if (layoutY < 0) {
return null;
}
return this.childLayout[layoutX][layoutY];
}
protected class CompositeLayerPainter implements ILayerPainter {
@Override
public void paintLayer(final ILayer natLayer, final GC gc,
final int xOffset, final int yOffset, final Rectangle pixelRectangle,
final IConfigRegistry configuration) {
int x= xOffset;
for (int layoutX= 0; layoutX < CompositeLayer.this.layoutXCount; layoutX++) {
int y= yOffset;
final int width= safe(CompositeLayer.this.childLayout[layoutX][0].layer.getWidth());
for (int layoutY= 0; layoutY < CompositeLayer.this.layoutYCount; layoutY++) {
final Child child= CompositeLayer.this.childLayout[layoutX][layoutY];
final Rectangle childRectangle= new Rectangle(x, y, width, safe(child.layer.getHeight()) );
final Rectangle childPaintRectangle= pixelRectangle.intersection(childRectangle);
if (!childPaintRectangle.isEmpty()) {
final Rectangle originalClipping= gc.getClipping();
gc.setClipping(childPaintRectangle);
child.layer.getLayerPainter().paintLayer(natLayer, gc,
x, y, childPaintRectangle, configuration );
gc.setClipping(originalClipping);
}
y+= childRectangle.height;
}
x+= width;
}
}
@Override
public LRectangle adjustCellBounds(final long columnPosition, final long rowPosition, final LRectangle cellBounds) {
final Child child= getChildByPosition(columnPosition, rowPosition);
final long widthOffset= get(HORIZONTAL).getLayoutStart(child.layoutX);
final long heightOffset= get(VERTICAL).getLayoutStart(child.layoutY);
// LRectangle bounds= new LRectangle(cellBounds.x - widthOffset, cellBounds.y - heightOffset, cellBounds.width, cellBounds.height);
cellBounds.x-= widthOffset;
cellBounds.y-= heightOffset;
final ILayerPainter childLayerPainter= child.layer.getLayerPainter();
final long childColumnPosition= columnPosition - get(HORIZONTAL).getLayoutPosition(child.layoutX);
final long childRowPosition= rowPosition - get(VERTICAL).getLayoutPosition(child.layoutY);
final LRectangle adjustedChildCellBounds= childLayerPainter.adjustCellBounds(childColumnPosition, childRowPosition, cellBounds);
// LRectangle adjustedChildCellBounds= childLayerPainter.adjustCellBounds(childColumnPosition, childRowPosition, bounds);
adjustedChildCellBounds.x+= widthOffset;
adjustedChildCellBounds.y+= heightOffset;
return adjustedChildCellBounds;
}
}
}