blob: c3fb9880f18864854c100be6059bfa40e4563445 [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.painter.cell.decorator;
import static org.eclipse.statet.ecommons.waltable.painter.cell.GraphicsUtils.safe;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.statet.ecommons.waltable.config.IConfigRegistry;
import org.eclipse.statet.ecommons.waltable.coordinate.LRectangle;
import org.eclipse.statet.ecommons.waltable.layer.cell.ILayerCell;
import org.eclipse.statet.ecommons.waltable.painter.cell.ICellPainter;
import org.eclipse.statet.ecommons.waltable.style.CellStyleAttributes;
import org.eclipse.statet.ecommons.waltable.style.CellStyleUtil;
import org.eclipse.statet.ecommons.waltable.ui.util.CellEdgeEnum;
/**
* Decorates a cell painter with another cell painter.
*/
public class CellPainterDecorator implements ICellPainter {
/**
* The base {@link ICellPainter} that is decorated.
*/
private final ICellPainter baseCellPainter;
/**
* The edge of the cell at which the decoration is applied.
*/
private final CellEdgeEnum cellEdge;
/**
* The {@link ICellPainter} that is used to render the decoration.
*/
private final ICellPainter decoratorCellPainter;
/**
* The spacing to use between base painter and decoration painter.
* Note: If you want to add <b>padding</b> between the decoration and the cell border
* you need to add a PaddingDecorator to your painter stack.
*/
private final long spacing;
/**
* Flag to specify whether the base painter should render dependent to the decoration painter
* or not. This will have effect on the boundary calculation. Setting this flag to <code>true</code>
* the bounds of the base painter will be modified regarding the bounds of the decoration painter.
* This means that the starting coordinates for the base painter are moving e.g. if the base painter
* renders centered the text will move to the left because the decoration consumes space.
* If this flag is set to <code>false</code> you can think of the decoration painter painting on
* top of the base painter, possibly painting over the base painter.
*/
private boolean paintDecorationDependent;
/**
* Will create a {@link CellPainterDecorator} with the default spacing of 2 between base and
* decoration painter, where the base painter is rendered dependent to the decoration.
* @param baseCellPainter The base {@link ICellPainter} that should be decorated
* @param cellEdge The edge of the cell at which the decoration should be applied
* @param decoratorCellPainter The {@link ICellPainter} that should be used to render the decoration.
*/
public CellPainterDecorator(final ICellPainter baseCellPainter, final CellEdgeEnum cellEdge, final ICellPainter decoratorCellPainter) {
this(baseCellPainter, cellEdge, 2, decoratorCellPainter);
}
/**
* Will create a {@link CellPainterDecorator} with the default spacing of 2 between base and
* decoration painter. If paintDecorationDependent is set to <code>false</code>, the spacing will be ignored.
* @param baseCellPainter The base {@link ICellPainter} that should be decorated
* @param cellEdge The edge of the cell at which the decoration should be applied
* @param decoratorCellPainter The {@link ICellPainter} that should be used to render the decoration.
* @param paintDecorationDependent Flag to specify whether the base painter should render dependent to the
* decoration painter or not.
*/
public CellPainterDecorator(final ICellPainter baseCellPainter, final CellEdgeEnum cellEdge, final ICellPainter decoratorCellPainter, final boolean paintDecorationDependent) {
this(baseCellPainter, cellEdge, 2, decoratorCellPainter, paintDecorationDependent);
}
/**
* Will create a {@link CellPainterDecorator} with the given amount of pixels as spacing between base and
* decoration painter, where the base painter is rendered dependent to the decoration.
* @param baseCellPainter The base {@link ICellPainter} that should be decorated
* @param cellEdge The edge of the cell at which the decoration should be applied
* @param spacing The amount of pixels that should be used as spacing between decoration and base painter
* @param decoratorCellPainter The {@link ICellPainter} that should be used to render the decoration.
*/
public CellPainterDecorator(final ICellPainter baseCellPainter, final CellEdgeEnum cellEdge, final long spacing, final ICellPainter decoratorCellPainter) {
this(baseCellPainter, cellEdge, spacing, decoratorCellPainter, true);
}
/**
* Will create a {@link CellPainterDecorator} with the given amount of pixels as spacing between base and
* decoration painter. If paintDecorationDependent is set to <code>false</code>, the spacing will be ignored
* while the decoration is mainly rendered over the base painter.
* @param baseCellPainter The base {@link ICellPainter} that should be decorated
* @param cellEdge The edge of the cell at which the decoration should be applied
* @param decoratorCellPainter The {@link ICellPainter} that should be used to render the decoration.
* @param paintDecorationDependent Flag to specify whether the base painter should render dependent to the
* decoration painter or not.
*/
public CellPainterDecorator(final ICellPainter baseCellPainter, final CellEdgeEnum cellEdge, final long spacing, final ICellPainter decoratorCellPainter, final boolean paintDecorationDependent) {
this.baseCellPainter= baseCellPainter;
this.cellEdge= cellEdge;
this.spacing= spacing;
this.decoratorCellPainter= decoratorCellPainter;
this.paintDecorationDependent= paintDecorationDependent;
}
/**
* @param paintDecorationDependent <code>true</code> if the base painter should render dependent to
* the decoration painter, <code>false</code> if the decoration should be rendered over
* the base painter.
*/
public void setPaintDecorationDependent(final boolean paintDecorationDependent) {
this.paintDecorationDependent= paintDecorationDependent;
}
@Override
public long getPreferredWidth(final ILayerCell cell, final GC gc, final IConfigRegistry configRegistry) {
switch (this.cellEdge) {
case TOP_LEFT:
case TOP_RIGHT:
case BOTTOM_LEFT:
case BOTTOM_RIGHT:
case BOTTOM:
case TOP:
return this.spacing
+ Math.max(
this.baseCellPainter.getPreferredWidth(cell, gc, configRegistry),
this.decoratorCellPainter.getPreferredWidth(cell, gc, configRegistry)
);
default:
break;
}
return this.baseCellPainter.getPreferredWidth(cell, gc, configRegistry)
+ this.spacing
+ this.decoratorCellPainter.getPreferredWidth(cell, gc, configRegistry);
}
@Override
public long getPreferredHeight(final ILayerCell cell, final GC gc, final IConfigRegistry configRegistry) {
switch (this.cellEdge) {
case TOP_LEFT:
case TOP_RIGHT:
case BOTTOM_LEFT:
case BOTTOM_RIGHT:
case LEFT:
case RIGHT:
return this.spacing
+ Math.max(
this.baseCellPainter.getPreferredHeight(cell, gc, configRegistry),
this.decoratorCellPainter.getPreferredHeight(cell, gc, configRegistry)
);
default:
break;
}
return this.baseCellPainter.getPreferredHeight(cell, gc, configRegistry)
+ this.spacing
+ this.decoratorCellPainter.getPreferredHeight(cell, gc, configRegistry);
}
@Override
public void paintCell(final ILayerCell cell, final GC gc, final LRectangle adjustedCellBounds, final IConfigRegistry configRegistry) {
final LRectangle baseCellPainterBounds= this.paintDecorationDependent ?
getBaseCellPainterBounds(cell, gc, adjustedCellBounds, configRegistry) : adjustedCellBounds;
final LRectangle decoratorCellPainterBounds= getDecoratorCellPainterBounds(cell, gc, adjustedCellBounds, configRegistry);
final Color originalBg= gc.getBackground();
gc.setBackground(CellStyleUtil.getCellStyle(cell, configRegistry).getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR));
gc.fillRectangle(safe(adjustedCellBounds));
gc.setBackground(originalBg);
this.baseCellPainter.paintCell(cell, gc, baseCellPainterBounds, configRegistry);
this.decoratorCellPainter.paintCell(cell, gc, decoratorCellPainterBounds, configRegistry);
}
/**
*
* @return The LRectangle which can be used by the base cell painter.
*/
public LRectangle getBaseCellPainterBounds(final ILayerCell cell, final GC gc, final LRectangle adjustedCellBounds, final IConfigRegistry configRegistry) {
final long preferredDecoratorWidth= this.decoratorCellPainter.getPreferredWidth(cell, gc, configRegistry);
final long preferredDecoratorHeight= this.decoratorCellPainter.getPreferredHeight(cell, gc, configRegistry);
// grab any extra space:
final long grabbedPreferredWidth= adjustedCellBounds.width - preferredDecoratorWidth - this.spacing;
final long grabbedPreferredHeight= adjustedCellBounds.height - preferredDecoratorHeight - this.spacing;
switch (this.cellEdge) {
case LEFT:
return new LRectangle(
adjustedCellBounds.x + preferredDecoratorWidth + this.spacing,
adjustedCellBounds.y,
grabbedPreferredWidth,
adjustedCellBounds.height
).intersection(adjustedCellBounds);
case RIGHT:
return new LRectangle(
adjustedCellBounds.x,
adjustedCellBounds.y,
grabbedPreferredWidth,
adjustedCellBounds.height
).intersection(adjustedCellBounds);
case TOP:
return new LRectangle(
adjustedCellBounds.x,
adjustedCellBounds.y + preferredDecoratorHeight + this.spacing,
adjustedCellBounds.width,
grabbedPreferredHeight
).intersection(adjustedCellBounds);
case BOTTOM:
return new LRectangle(
adjustedCellBounds.x,
adjustedCellBounds.y,
adjustedCellBounds.width,
grabbedPreferredHeight
).intersection(adjustedCellBounds);
case TOP_LEFT:
return new LRectangle(
adjustedCellBounds.x + preferredDecoratorWidth + this.spacing,
adjustedCellBounds.y + preferredDecoratorHeight + this.spacing,
grabbedPreferredWidth,
grabbedPreferredHeight
).intersection(adjustedCellBounds);
case TOP_RIGHT:
return new LRectangle(
adjustedCellBounds.x,
adjustedCellBounds.y + preferredDecoratorHeight + this.spacing,
grabbedPreferredWidth,
grabbedPreferredHeight
).intersection(adjustedCellBounds);
case BOTTOM_LEFT:
return new LRectangle(
adjustedCellBounds.x + preferredDecoratorWidth + this.spacing,
adjustedCellBounds.y,
grabbedPreferredWidth,
grabbedPreferredHeight
).intersection(adjustedCellBounds);
case BOTTOM_RIGHT:
return new LRectangle(
adjustedCellBounds.x,
adjustedCellBounds.y,
grabbedPreferredWidth,
grabbedPreferredHeight
).intersection(adjustedCellBounds);
case NONE:
break;
}
return null;
}
/**
* @return The LRectangle to paint the decoration.
*/
public LRectangle getDecoratorCellPainterBounds(final ILayerCell cell, final GC gc, final LRectangle adjustedCellBounds, final IConfigRegistry configRegistry) {
final long preferredDecoratorWidth= this.decoratorCellPainter.getPreferredWidth(cell, gc, configRegistry);
final long preferredDecoratorHeight= this.decoratorCellPainter.getPreferredHeight(cell, gc, configRegistry);
switch (this.cellEdge) {
case LEFT:
return new LRectangle(
adjustedCellBounds.x,
adjustedCellBounds.y + ((adjustedCellBounds.height - preferredDecoratorHeight) / 2 ),
preferredDecoratorWidth,
preferredDecoratorHeight);
case RIGHT:
return new LRectangle(
adjustedCellBounds.x + adjustedCellBounds.width - preferredDecoratorWidth,
adjustedCellBounds.y + ((adjustedCellBounds.height - preferredDecoratorHeight) / 2 ),
preferredDecoratorWidth,
preferredDecoratorHeight);
case TOP:
return new LRectangle(
adjustedCellBounds.x + ((adjustedCellBounds.width - preferredDecoratorWidth) / 2),
adjustedCellBounds.y,
preferredDecoratorWidth,
preferredDecoratorHeight);
case BOTTOM:
return new LRectangle(
adjustedCellBounds.x + ((adjustedCellBounds.width - preferredDecoratorWidth) / 2),
adjustedCellBounds.y + adjustedCellBounds.height - preferredDecoratorHeight,
preferredDecoratorWidth,
preferredDecoratorHeight);
case TOP_LEFT:
return new LRectangle(
adjustedCellBounds.x,
adjustedCellBounds.y,
preferredDecoratorWidth,
preferredDecoratorHeight);
case TOP_RIGHT:
return new LRectangle(
adjustedCellBounds.x + adjustedCellBounds.width - preferredDecoratorWidth,
adjustedCellBounds.y,
preferredDecoratorWidth,
preferredDecoratorHeight);
case BOTTOM_LEFT:
return new LRectangle(
adjustedCellBounds.x,
adjustedCellBounds.y + adjustedCellBounds.height - preferredDecoratorHeight,
preferredDecoratorWidth,
preferredDecoratorHeight);
case BOTTOM_RIGHT:
return new LRectangle(
adjustedCellBounds.x + adjustedCellBounds.width - preferredDecoratorWidth,
adjustedCellBounds.y + adjustedCellBounds.height - preferredDecoratorHeight,
preferredDecoratorWidth,
preferredDecoratorHeight);
case NONE:
break;
}
return null;
}
@Override
public ICellPainter getCellPainterAt(final long x, final long y, final ILayerCell cell, final GC gc, final LRectangle adjustedCellBounds, final IConfigRegistry configRegistry) {
final LRectangle decoratorCellPainterBounds= getDecoratorCellPainterBounds(cell, gc, adjustedCellBounds, configRegistry);
if (decoratorCellPainterBounds.contains(x, y)) {
return this.decoratorCellPainter.getCellPainterAt(x, y, cell, gc, decoratorCellPainterBounds, configRegistry);
} else {
final LRectangle baseCellPainterBounds= this.paintDecorationDependent ?
getBaseCellPainterBounds(cell, gc, adjustedCellBounds, configRegistry) : adjustedCellBounds;
if (baseCellPainterBounds.contains(x, y)) {
return this.baseCellPainter.getCellPainterAt(x, y, cell, gc, baseCellPainterBounds, configRegistry);
}
}
return this;
}
}