/* * Copyright 2015 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.grids.impl; import java.util.List; import com.ait.lienzo.client.core.shape.Group; import com.ait.lienzo.client.core.shape.Line; import com.ait.lienzo.client.core.shape.MultiPath; import com.ait.lienzo.client.core.shape.Rectangle; import com.ait.lienzo.client.core.shape.Text; import com.ait.lienzo.client.core.types.Point2D; import com.ait.lienzo.client.core.types.Point2DArray; import com.ait.lienzo.client.core.types.Transform; import org.uberfire.commons.validation.PortablePreconditions; 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.widget.context.GridBodyColumnRenderContext; import org.uberfire.ext.wires.core.grids.client.widget.context.GridBodyRenderContext; import org.uberfire.ext.wires.core.grids.client.widget.context.GridHeaderColumnRenderContext; import org.uberfire.ext.wires.core.grids.client.widget.context.GridHeaderRenderContext; 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.SelectionsTransformer; import org.uberfire.ext.wires.core.grids.client.widget.grid.renderers.themes.GridRendererTheme; /** * A renderer that only renders the visible columns and rows of merged data. This implementation * can render the data either in a merged state or non-merged state. */ public class BaseGridRenderer implements GridRenderer { private static final int HEADER_HEIGHT = 64; private static final int HEADER_ROW_HEIGHT = 32; private static final String LINK_FONT_FAMILY = "Glyphicons Halflings"; private static final double LINK_FONT_SIZE = 10.0; private static final String LINK_ICON = "\ue144"; protected GridRendererTheme theme; public BaseGridRenderer(final GridRendererTheme theme) { setTheme(theme); } @Override public double getHeaderHeight() { return HEADER_HEIGHT; } @Override public double getHeaderRowHeight() { return HEADER_ROW_HEIGHT; } @Override public GridRendererTheme getTheme() { return theme; } @Override public void setTheme(final GridRendererTheme theme) { this.theme = PortablePreconditions.checkNotNull("theme", theme); } @Override public Group renderSelector(final double width, final double height, final BaseGridRendererHelper.RenderingInformation renderingInformation) { final Group g = new Group(); final MultiPath selector = theme.getSelector() .M(0.5, 0.5) .L(0.5, height) .L(width, height) .L(width, 0.5) .L(0.5, 0.5) .setListening(false); g.add(selector); return g; } @Override public Group renderSelectedCells(final GridData model, final GridBodyRenderContext context, final BaseGridRendererHelper rendererHelper) { final List<GridColumn<?>> blockColumns = context.getBlockColumns(); final SelectionsTransformer transformer = context.getTransformer(); final int minVisibleUiColumnIndex = model.getColumns().indexOf(blockColumns.get(0)); final int maxVisibleUiColumnIndex = model.getColumns().indexOf(blockColumns.get(blockColumns.size() - 1)); final int minVisibleUiRowIndex = context.getMinVisibleRowIndex(); final int maxVisibleUiRowIndex = context.getMaxVisibleRowIndex(); //Convert SelectedCells into SelectedRanges, i.e. group them into rectangular ranges final List<SelectedRange> selectedRanges = transformer.transformToSelectedRanges(); final Group g = new Group(); for (SelectedRange selectedRange : selectedRanges) { final int rangeOriginUiColumnIndex = selectedRange.getUiColumnIndex(); final int rangeOriginUiRowIndex = selectedRange.getUiRowIndex(); final int rangeUiWidth = selectedRange.getWidth(); final int rangeUiHeight = selectedRange.getHeight(); //Only render range highlights if they're at least partially visible if (rangeOriginUiColumnIndex + rangeUiWidth - 1 < minVisibleUiColumnIndex) { continue; } if (rangeOriginUiColumnIndex > maxVisibleUiColumnIndex) { continue; } if (rangeOriginUiRowIndex + rangeUiHeight - 1 < minVisibleUiRowIndex) { continue; } if (rangeOriginUiRowIndex > maxVisibleUiRowIndex) { continue; } //Clip range to visible bounds SelectedRange _selectedRange = selectedRange; if (rangeOriginUiRowIndex < minVisibleUiRowIndex) { final int dy = minVisibleUiRowIndex - rangeOriginUiRowIndex; _selectedRange = new SelectedRange(selectedRange.getUiRowIndex() + dy, selectedRange.getUiColumnIndex(), selectedRange.getWidth(), selectedRange.getHeight() - dy); } final Group cs = renderSelectedRange(model, blockColumns, minVisibleUiColumnIndex, _selectedRange); if (cs != null) { final double csx = rendererHelper.getColumnOffset(blockColumns, _selectedRange.getUiColumnIndex() - minVisibleUiColumnIndex); final double csy = rendererHelper.getRowOffset(_selectedRange.getUiRowIndex()) - rendererHelper.getRowOffset(minVisibleUiRowIndex); cs.setX(csx) .setY(csy) .setListening(false); g.add(cs); } } return g; } protected Group renderSelectedRange(final GridData model, final List<GridColumn<?>> blockColumns, final int minVisibleUiColumnIndex, final SelectedRange selectedRange) { final Group cellSelector = new Group(); final double width = getSelectedRangeWidth(blockColumns, minVisibleUiColumnIndex, selectedRange); final double height = getSelectedRangeHeight(model, selectedRange); final Rectangle selector = theme.getCellSelector() .setWidth(width) .setHeight(height) .setStrokeWidth(1.0) .setListening(false); final Rectangle highlight = theme.getCellSelector() .setWidth(width) .setHeight(height) .setFillColor(selector.getStrokeColor()) .setListening(false) .setAlpha(0.25); cellSelector.add(highlight); cellSelector.add(selector); return cellSelector; } private double getSelectedRangeWidth(final List<GridColumn<?>> blockColumns, final int minVisibleUiColumnIndex, final SelectedRange selectedRange) { double width = 0; for (int columnIndex = 0; columnIndex < selectedRange.getWidth(); columnIndex++) { final int relativeColumnIndex = columnIndex + selectedRange.getUiColumnIndex() - minVisibleUiColumnIndex; width = width + blockColumns.get(relativeColumnIndex).getWidth(); } return width; } private double getSelectedRangeHeight(final GridData model, final SelectedRange selectedRange) { double height = 0; for (int rowIndex = 0; rowIndex < selectedRange.getHeight(); rowIndex++) { height = height + model.getRow(selectedRange.getUiRowIndex() + rowIndex).getHeight(); } return height; } @Override public Group renderHeader(final GridData model, final GridHeaderRenderContext context, final BaseGridRendererHelper rendererHelper, final BaseGridRendererHelper.RenderingInformation renderingInformation) { final List<GridColumn<?>> allBlockColumns = context.getAllColumns(); final List<GridColumn<?>> visibleBlockColumns = context.getBlockColumns(); final boolean isSelectionLayer = context.isSelectionLayer(); final double headerRowsHeight = renderingInformation.getHeaderRowsHeight(); final double headerRowsYOffset = renderingInformation.getHeaderRowsYOffset(); final Group g = new Group(); //Column backgrounds double x = 0; for (final GridColumn<?> column : visibleBlockColumns) { if (column.isVisible()) { final double w = column.getWidth(); Rectangle header; if (column.isLinked()) { header = theme.getHeaderLinkBackground(column); } else { header = theme.getHeaderBackground(column); } if (header != null) { header.setWidth(w) .setListening(true) .setHeight(headerRowsHeight) .setY(headerRowsYOffset) .setX(x); g.add(header); } x = x + w; } } //Don't render the Header's detail if we're rendering the SelectionLayer if (isSelectionLayer) { return g; } //Column title and grid lines x = 0; for (final GridColumn<?> column : visibleBlockColumns) { if (column.isVisible()) { final double columnWidth = column.getWidth(); final int columnIndex = visibleBlockColumns.indexOf(column); final GridHeaderColumnRenderContext headerCellRenderContext = new GridHeaderColumnRenderContext(allBlockColumns, visibleBlockColumns, columnIndex, model, this); final Group headerGroup = column.getColumnRenderer().renderHeader(column.getHeaderMetaData(), headerCellRenderContext, renderingInformation); headerGroup.setX(x); g.add(headerGroup); x = x + columnWidth; } } //Linked column icons x = 0; for (final GridColumn<?> column : visibleBlockColumns) { if (column.isVisible()) { final double w = column.getWidth(); if (column.isLinked()) { final Text t = theme.getBodyText() .setFontFamily(LINK_FONT_FAMILY) .setFontSize(LINK_FONT_SIZE) .setText(LINK_ICON) .setY(headerRowsYOffset + LINK_FONT_SIZE) .setX(x + w - LINK_FONT_SIZE); g.add(t); } x = x + w; } } //Divider between header and body final Group divider = renderHeaderBodyDivider(x); g.add(divider); return g; } @Override public Group renderHeaderBodyDivider(final double width) { final Group g = new Group(); final Line divider = theme.getGridHeaderBodyDivider(); divider.setPoints(new Point2DArray(new Point2D(0, getHeaderHeight() + 0.5), new Point2D(width, getHeaderHeight() + 0.5))); g.add(divider); return g; } @Override public Group renderBody(final GridData model, final GridBodyRenderContext context, final BaseGridRendererHelper rendererHelper, final BaseGridRendererHelper.RenderingInformation renderingInformation) { final double absoluteGridX = context.getAbsoluteGridX(); final double absoluteGridY = context.getAbsoluteGridY(); final double absoluteColumnOffsetX = context.getAbsoluteColumnOffsetX(); final double clipMinY = context.getClipMinY(); final double clipMinX = context.getClipMinX(); final int minVisibleRowIndex = context.getMinVisibleRowIndex(); final int maxVisibleRowIndex = context.getMaxVisibleRowIndex(); final List<GridColumn<?>> blockColumns = context.getBlockColumns(); final boolean isSelectionLayer = context.isSelectionLayer(); final Transform transform = context.getTransform(); final GridRenderer renderer = context.getRenderer(); final BaseGridRendererHelper.RenderingBlockInformation floatingBlockInformation = renderingInformation.getFloatingBlockInformation(); final List<Double> visibleRowOffsets = renderingInformation.getVisibleRowOffsets(); final double columnHeight = visibleRowOffsets.get(maxVisibleRowIndex - minVisibleRowIndex) - visibleRowOffsets.get(0) + model.getRow(maxVisibleRowIndex).getHeight(); final Group g = new Group(); //Column backgrounds double x = 0; for (final GridColumn<?> column : blockColumns) { if (column.isVisible()) { final double columnWidth = column.getWidth(); final Rectangle body = theme.getBodyBackground(column) .setWidth(columnWidth) .setListening(true) .setHeight(columnHeight) .setX(x); g.add(body); x = x + columnWidth; } } //Don't render the Body's detail if we're rendering the SelectionLayer if (isSelectionLayer) { return g; } x = 0; for (GridColumn<?> column : blockColumns) { if (column.isVisible()) { final double columnWidth = column.getWidth(); final double columnRelativeX = rendererHelper.getColumnOffset(blockColumns, blockColumns.indexOf(column)) + absoluteColumnOffsetX; final boolean isFloating = floatingBlockInformation.getColumns().contains(column); final GridBodyColumnRenderContext columnContext = new GridBodyColumnRenderContext(absoluteGridX, absoluteGridY, absoluteGridX + columnRelativeX, clipMinY, clipMinX, minVisibleRowIndex, maxVisibleRowIndex, isFloating, model, transform, renderer); final Group columnGroup = column.getColumnRenderer().renderColumn(column, columnContext, rendererHelper, renderingInformation); columnGroup.setX(x); g.add(columnGroup); x = x + columnWidth; } } return g; } @Override public Group renderGridBoundary(final double width, final double height) { final Group g = new Group(); final Rectangle boundary = theme.getGridBoundary() .setWidth(width) .setHeight(height) .setListening(false) .setX(0.5) .setY(0.5); g.add(boundary); return g; } @Override public boolean onGroupingToggle(double cellX, double cellY, double cellWidth, double cellHeight) { return GroupingToggle.onHotSpot(cellX, cellY, cellWidth, cellHeight); } }