/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.uberfire.ext.wires.core.grids.client.widget.grid.renderers.columns.impl;
import java.util.List;
import com.ait.lienzo.client.core.shape.BoundingBoxPathClipper;
import com.ait.lienzo.client.core.shape.Group;
import com.ait.lienzo.client.core.shape.IPathClipper;
import com.ait.lienzo.client.core.shape.MultiPath;
import com.ait.lienzo.client.core.shape.Rectangle;
import com.ait.lienzo.client.core.types.BoundingBox;
import com.ait.lienzo.client.core.types.Transform;
import com.ait.lienzo.shared.core.types.ColorName;
import org.uberfire.ext.wires.core.grids.client.model.GridCell;
import org.uberfire.ext.wires.core.grids.client.model.GridColumn;
import org.uberfire.ext.wires.core.grids.client.model.GridData;
import org.uberfire.ext.wires.core.grids.client.model.GridRow;
import org.uberfire.ext.wires.core.grids.client.widget.context.GridBodyCellRenderContext;
import org.uberfire.ext.wires.core.grids.client.widget.context.GridBodyColumnRenderContext;
import org.uberfire.ext.wires.core.grids.client.widget.grid.renderers.grids.GridRenderer;
import org.uberfire.ext.wires.core.grids.client.widget.grid.renderers.grids.impl.BaseGridRendererHelper;
import org.uberfire.ext.wires.core.grids.client.widget.grid.renderers.grids.impl.GroupingToggle;
import org.uberfire.ext.wires.core.grids.client.widget.grid.renderers.themes.GridRendererTheme;
public class ColumnRenderingStrategyMerged {
public Group render(final GridColumn<?> column,
final GridBodyColumnRenderContext context,
final BaseGridRendererHelper rendererHelper,
final BaseGridRendererHelper.RenderingInformation renderingInformation) {
final double absoluteGridY = context.getAbsoluteGridY();
final double absoluteColumnX = context.getAbsoluteColumnX();
final double clipMinY = context.getClipMinY();
final double clipMinX = context.getClipMinX();
final int minVisibleRowIndex = context.getMinVisibleRowIndex();
final int maxVisibleRowIndex = context.getMaxVisibleRowIndex();
final List<Double> visibleRowOffsets = renderingInformation.getVisibleRowOffsets();
final boolean isFloating = context.isFloating();
final GridData model = context.getModel();
final Transform transform = context.getTransform();
final GridRenderer renderer = context.getRenderer();
final GridRendererTheme theme = renderer.getTheme();
final Group g = new Group();
final double columnWidth = column.getWidth();
final double columnHeight = visibleRowOffsets.get(maxVisibleRowIndex - minVisibleRowIndex) - visibleRowOffsets.get(0) + model.getRow(maxVisibleRowIndex).getHeight();
//Grid lines - horizontal
final MultiPath bodyGrid = theme.getBodyGridLine();
for (int rowIndex = minVisibleRowIndex; rowIndex <= maxVisibleRowIndex; rowIndex++) {
final double y = visibleRowOffsets.get(rowIndex - minVisibleRowIndex) - visibleRowOffsets.get(0);
final GridRow row = model.getRow(rowIndex);
if (!row.isMerged()) {
//If row doesn't contain merged cells just draw a line across the visible body
bodyGrid.M(0,
y + 0.5).L(columnWidth,
y + 0.5);
} else if (!row.isCollapsed()) {
//If row isn't collapsed just draw a line across the visible body at the top of the merged block
final int columnIndex = model.getColumns().indexOf(column);
final GridCell<?> cell = model.getCell(rowIndex,
columnIndex);
if (cell == null || cell.getMergedCellCount() > 0) {
//Draw a line-segment for empty cells and cells that are to have content rendered
bodyGrid.M(0,
y + 0.5).L(columnWidth,
y + 0.5);
} else if (isCollapsedRowMultiValue(model,
column,
cell,
rowIndex)) {
//Special case for when a cell follows collapsed row(s) with multiple values
bodyGrid.M(0,
y + 0.5).L(columnWidth,
y + 0.5);
}
}
}
//Grid lines - vertical
final int columnIndex = model.getColumns().indexOf(column);
if (columnIndex < model.getColumnCount() - 1) {
bodyGrid.M(columnWidth + 0.5,
0).L(columnWidth + 0.5,
columnHeight);
}
//Column content
final Group columnGroup = new Group();
int iterations = 0;
for (int rowIndex = minVisibleRowIndex; rowIndex <= maxVisibleRowIndex; rowIndex++) {
iterations++;
if (iterations > 1000) {
break;
}
final double y = visibleRowOffsets.get(rowIndex - minVisibleRowIndex) - visibleRowOffsets.get(0);
final GridRow row = model.getRow(rowIndex);
final GridCell<?> cell = model.getCell(rowIndex,
columnIndex);
//Only show content for rows that are not collapsed
if (row.isCollapsed()) {
continue;
}
//Add highlight for merged cells with different values
final boolean isCollapsedCellMixedValue = isCollapsedCellMixedValue(model,
rowIndex,
columnIndex);
if (isCollapsedCellMixedValue) {
final Group mixedValueGroup = renderMergedCellMixedValueHighlight(columnWidth,
row.getHeight());
mixedValueGroup.setX(0)
.setY(y)
.setListening(false);
g.add(mixedValueGroup);
}
//Only show content if there's a Cell behind it!
if (cell == null) {
continue;
}
//Add Group Toggle for first row in a Merged block
if (cell.getMergedCellCount() > 1) {
final GridCell<?> nextRowCell = model.getCell(rowIndex + 1,
columnIndex);
if (nextRowCell != null) {
final Group gt = renderGroupedCellToggle(columnWidth,
row.getHeight(),
nextRowCell.isCollapsed());
gt.setX(0).setY(y);
g.add(gt);
}
}
if (cell.getMergedCellCount() > 0) {
//If cell is "lead" i.e. top of a merged block centralize content in cell
final double cellHeight = getCellHeight(rowIndex,
model,
cell);
final GridBodyCellRenderContext cellContext = new GridBodyCellRenderContext(absoluteColumnX,
absoluteGridY + renderer.getHeaderHeight() + visibleRowOffsets.get(rowIndex - minVisibleRowIndex),
columnWidth,
cellHeight,
clipMinY,
clipMinX,
rowIndex,
columnIndex,
isFloating,
transform,
renderer);
//Render cell's content
final Group cc = column.getColumnRenderer().renderCell((GridCell) cell,
cellContext);
cc.setX(0)
.setY(y)
.setListening(false);
columnGroup.add(cc);
//Skip remainder of merged block
rowIndex = rowIndex + cell.getMergedCellCount() - 1;
} else {
//Otherwise the cell has been clipped and we need to back-track to the "lead" cell to centralize content
double _y = y;
int _rowIndex = rowIndex;
GridCell<?> _cell = cell;
while (_cell.getMergedCellCount() == 0) {
_rowIndex--;
_y = _y - model.getRow(_rowIndex).getHeight();
_cell = model.getCell(_rowIndex,
columnIndex);
}
final double cellHeight = getCellHeight(_rowIndex,
model,
_cell);
final GridBodyCellRenderContext cellContext = new GridBodyCellRenderContext(absoluteColumnX,
absoluteGridY + renderer.getHeaderHeight() + rendererHelper.getRowOffset(_rowIndex),
columnWidth,
cellHeight,
clipMinY,
clipMinX,
rowIndex,
columnIndex,
isFloating,
transform,
renderer);
//Render cell's content
final Group cc = column.getColumnRenderer().renderCell((GridCell) _cell,
cellContext);
cc.setX(0)
.setY(_y)
.setListening(false);
columnGroup.add(cc);
//Skip remainder of merged block
rowIndex = _rowIndex + _cell.getMergedCellCount() - 1;
}
}
//Clip Column Group
final double gridLinesStrokeWidth = theme.getBodyGridLine().getStrokeWidth();
final BoundingBox bb = new BoundingBox(gridLinesStrokeWidth,
0,
columnWidth - gridLinesStrokeWidth,
columnHeight);
final IPathClipper clipper = new BoundingBoxPathClipper(bb);
columnGroup.setPathClipper(clipper);
clipper.setActive(true);
g.add(columnGroup);
g.add(bodyGrid);
return g;
}
private boolean isCollapsedRowMultiValue(final GridData model,
final GridColumn<?> column,
final GridCell<?> cell,
final int rowIndex) {
GridRow row;
int rowOffset = 1;
final int columnIndex = column.getIndex();
//Iterate collapsed rows checking if the values differ
while ((row = model.getRow(rowIndex - rowOffset)).isCollapsed()) {
final GridCell<?> nc = row.getCells().get(columnIndex);
if (nc == null) {
return true;
}
if (!cell.getValue().equals(nc.getValue())) {
return true;
}
rowOffset++;
}
//Check "lead" row as well - since this is not marked as collapsed
final GridCell<?> nc = row.getCells().get(columnIndex);
if (nc == null) {
return true;
}
if (!cell.getValue().equals(nc.getValue())) {
return true;
}
return false;
}
private boolean isCollapsedCellMixedValue(final GridData model,
final int rowIndex,
final int columnIndex) {
int _rowIndex = rowIndex;
GridCell<?> currentCell = model.getCell(_rowIndex,
columnIndex);
if (currentCell != null) {
while (_rowIndex > 0 && currentCell.getMergedCellCount() == 0) {
_rowIndex--;
currentCell = model.getCell(_rowIndex,
columnIndex);
}
}
_rowIndex++;
if (_rowIndex > model.getRowCount() - 1) {
return false;
}
while (_rowIndex < model.getRowCount() && model.getRow(_rowIndex).isCollapsed()) {
final GridCell<?> nextCell = model.getCell(_rowIndex,
columnIndex);
if (currentCell == null) {
if (nextCell != null) {
return true;
}
} else {
if (nextCell == null) {
return true;
}
if (!currentCell.getValue().getValue().equals(nextCell.getValue().getValue())) {
return true;
}
}
_rowIndex++;
}
return false;
}
private double getCellHeight(final int rowIndex,
final GridData model,
final GridCell<?> cell) {
double height = 0;
for (int i = rowIndex; i < rowIndex + cell.getMergedCellCount(); i++) {
height = height + model.getRow(i).getHeight();
}
return height;
}
private Group renderGroupedCellToggle(final double cellWidth,
final double cellHeight,
final boolean isCollapsed) {
return new GroupingToggle(cellWidth,
cellHeight,
isCollapsed);
}
private Group renderMergedCellMixedValueHighlight(final double cellWidth,
final double cellHeight) {
final Group g = new Group();
final Rectangle multiValueHighlight = new Rectangle(cellWidth,
cellHeight);
multiValueHighlight.setFillColor(ColorName.GOLDENROD);
g.add(multiValueHighlight);
return g;
}
}