/*
* 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.impl;
import java.util.ArrayList;
import java.util.List;
import com.ait.lienzo.client.core.Context2D;
import com.ait.lienzo.client.core.event.NodeMouseClickEvent;
import com.ait.lienzo.client.core.event.NodeMouseClickHandler;
import com.ait.lienzo.client.core.event.NodeMouseDoubleClickHandler;
import com.ait.lienzo.client.core.shape.Group;
import com.ait.lienzo.client.core.types.BoundingBox;
import com.ait.lienzo.client.core.types.Point2D;
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.GridBodyRenderContext;
import org.uberfire.ext.wires.core.grids.client.widget.context.GridHeaderRenderContext;
import org.uberfire.ext.wires.core.grids.client.widget.dnd.GridWidgetDnDHandlersState;
import org.uberfire.ext.wires.core.grids.client.widget.dom.HasDOMElementResources;
import org.uberfire.ext.wires.core.grids.client.widget.dom.multiple.HasMultipleDOMElementResources;
import org.uberfire.ext.wires.core.grids.client.widget.grid.GridWidget;
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.grids.impl.BaseGridRendererHelper;
import org.uberfire.ext.wires.core.grids.client.widget.grid.renderers.grids.impl.DefaultSelectionsTransformer;
import org.uberfire.ext.wires.core.grids.client.widget.grid.renderers.grids.impl.FloatingSelectionsTransformer;
import org.uberfire.ext.wires.core.grids.client.widget.grid.selections.CellSelectionManager;
import org.uberfire.ext.wires.core.grids.client.widget.grid.selections.SelectionExtension;
import org.uberfire.ext.wires.core.grids.client.widget.grid.selections.impl.BaseCellSelectionManager;
import org.uberfire.ext.wires.core.grids.client.widget.layer.GridSelectionManager;
import org.uberfire.ext.wires.core.grids.client.widget.layer.impl.DefaultGridLayer;
import org.uberfire.ext.wires.core.grids.client.widget.layer.pinning.GridPinnedModeManager;
/**
* The base of all GridWidgets.
*/
public class BaseGridWidget extends Group implements GridWidget {
protected final SelectionsTransformer bodyTransformer;
protected final SelectionsTransformer floatingColumnsTransformer;
protected final BaseGridRendererHelper rendererHelper;
//These are final as a reference is held by the ISelectionsTransformers
protected final List<GridColumn<?>> allColumns = new ArrayList<GridColumn<?>>();
protected final List<GridColumn<?>> bodyColumns = new ArrayList<GridColumn<?>>();
protected final List<GridColumn<?>> floatingColumns = new ArrayList<GridColumn<?>>();
private final CellSelectionManager cellSelectionManager;
protected GridData model;
protected GridRenderer renderer;
protected Group header = null;
protected Group floatingHeader = null;
protected Group body = null;
protected Group floatingBody = null;
private Group selection = null;
private boolean isSelected = false;
public BaseGridWidget(final GridData model,
final GridSelectionManager selectionManager,
final GridPinnedModeManager pinnedModeManager,
final GridRenderer renderer) {
this.model = model;
this.renderer = renderer;
this.bodyTransformer = new DefaultSelectionsTransformer(model,
bodyColumns);
this.floatingColumnsTransformer = new FloatingSelectionsTransformer(model,
floatingColumns);
this.rendererHelper = getBaseGridRendererHelper();
this.cellSelectionManager = getCellSelectionManager();
//Click handlers
addNodeMouseClickHandler(getGridMouseClickHandler(selectionManager));
addNodeMouseClickHandler(getGridMouseCellSelectorClickHandler(selectionManager));
addNodeMouseDoubleClickHandler(getGridMouseDoubleClickHandler(selectionManager,
pinnedModeManager));
//NodeMouseUpEvent on GridLayer is not fired at a drag-end, so clear the state here.
addNodeDragEndHandler((event) -> {
final GridWidgetDnDHandlersState state = ((DefaultGridLayer) getLayer()).getGridWidgetHandlersState();
state.reset();
getViewport().getElement().getStyle().setCursor(state.getCursor());
});
}
BaseGridRendererHelper getBaseGridRendererHelper() {
return new BaseGridRendererHelper(this);
}
CellSelectionManager getCellSelectionManager() {
return new BaseCellSelectionManager(this);
}
NodeMouseClickHandler getGridMouseClickHandler(final GridSelectionManager selectionManager) {
return new BaseGridWidgetMouseClickHandler(this,
selectionManager,
renderer);
}
NodeMouseClickHandler getGridMouseCellSelectorClickHandler(final GridSelectionManager selectionManager) {
return new GridCellSelectorMouseClickHandler(this,
selectionManager,
renderer);
}
NodeMouseDoubleClickHandler getGridMouseDoubleClickHandler(final GridSelectionManager selectionManager,
final GridPinnedModeManager pinnedModeManager) {
return new BaseGridWidgetMouseDoubleClickHandler(this,
selectionManager,
pinnedModeManager,
renderer);
}
@Override
public GridData getModel() {
return model;
}
@Override
public GridRenderer getRenderer() {
return this.renderer;
}
@Override
public void setRenderer(final GridRenderer renderer) {
this.renderer = renderer;
}
@Override
public BaseGridRendererHelper getRendererHelper() {
return rendererHelper;
}
@Override
public Group getBody() {
return body;
}
@Override
public Group getHeader() {
return header;
}
@Override
public double getWidth() {
return rendererHelper.getWidth(model.getColumns());
}
@Override
public double getHeight() {
double height = renderer.getHeaderHeight();
height = height + rendererHelper.getRowOffset(model.getRowCount());
return height;
}
@Override
public void select() {
isSelected = true;
final BaseGridRendererHelper.RenderingInformation renderingInformation = rendererHelper.getRenderingInformation();
if (renderingInformation == null) {
return;
}
assertSelectionWidget(renderingInformation);
add(selection);
}
@Override
public void deselect() {
isSelected = false;
if (selection != null) {
remove(selection);
}
}
@Override
public boolean isSelected() {
return isSelected;
}
private void assertSelectionWidget(final BaseGridRendererHelper.RenderingInformation renderingInformation) {
this.selection = renderer.renderSelector(getWidth(),
getHeight(),
renderingInformation);
}
/**
* Intercept the normal Lienzo draw mechanism to calculate and hence draw only the visible
* columns and rows for the Grid; being those within the bounds of the GridLayer. At the
* start of this draw method all visible columns are given an opportunity to initialise
* any resources they require (e.g. DOMElements). At the end of this method all visible
* columns are given an opportunity to release any unused resources (e.g. DOMElements).
* If a column is not visible it is given an opportunity to destroy all resources.
* @param context
* @param alpha
*/
@Override
protected void drawWithoutTransforms(Context2D context,
double alpha,
BoundingBox bb) {
body = null;
header = null;
floatingBody = null;
floatingHeader = null;
if ((context.isSelection()) && (false == isListening())) {
return;
}
alpha = alpha * getAttributes().getAlpha();
if (alpha <= 0) {
return;
}
if (model.getColumns().isEmpty()) {
return;
}
//Clear existing content
this.removeAll();
this.allColumns.clear();
this.bodyColumns.clear();
this.floatingColumns.clear();
//If there's no RenderingInformation the GridWidget is not visible
final BaseGridRendererHelper.RenderingInformation renderingInformation = rendererHelper.getRenderingInformation();
if (renderingInformation == null) {
destroyDOMElementResources();
return;
}
final BaseGridRendererHelper.RenderingBlockInformation bodyBlockInformation = renderingInformation.getBodyBlockInformation();
final BaseGridRendererHelper.RenderingBlockInformation floatingBlockInformation = renderingInformation.getFloatingBlockInformation();
final List<GridColumn<?>> allColumns = renderingInformation.getAllColumns();
final List<GridColumn<?>> bodyColumns = bodyBlockInformation.getColumns();
final List<GridColumn<?>> floatingColumns = floatingBlockInformation.getColumns();
final boolean isSelectionLayer = context.isSelection();
this.allColumns.addAll(allColumns);
this.bodyColumns.addAll(bodyColumns);
this.floatingColumns.addAll(floatingColumns);
//Signal columns to attach or detach rendering support
if (!isSelectionLayer) {
for (GridColumn<?> column : model.getColumns()) {
if (bodyColumns.contains(column) || floatingColumns.contains(column)) {
if (column instanceof HasMultipleDOMElementResources) {
((HasMultipleDOMElementResources) column).initialiseResources();
}
} else if (column instanceof HasDOMElementResources) {
((HasDOMElementResources) column).destroyResources();
}
}
}
//Draw if required
if (this.bodyColumns.size() > 0) {
drawHeader(renderingInformation,
isSelectionLayer);
if (model.getRowCount() > 0) {
drawBody(renderingInformation,
isSelectionLayer);
}
if (body != null) {
add(body);
}
if (header != null) {
add(header);
}
if (body != null || header != null) {
add(renderGridBoundary(bodyColumns,
bodyBlockInformation.getX(),
renderingInformation));
}
if (floatingBody != null) {
add(floatingBody);
}
if (floatingHeader != null) {
add(floatingHeader);
}
if (floatingBody != null || floatingHeader != null) {
add(renderGridBoundary(floatingColumns,
floatingBlockInformation.getX(),
renderingInformation));
}
//Include selection indicator if required
if (isSelected) {
assertSelectionWidget(renderingInformation);
add(selection);
}
}
//Signal columns to free any unused resources
if (!isSelectionLayer) {
for (GridColumn<?> column : bodyColumns) {
if (column instanceof HasMultipleDOMElementResources) {
((HasMultipleDOMElementResources) column).freeUnusedResources();
}
}
for (GridColumn<?> column : floatingColumns) {
if (column instanceof HasMultipleDOMElementResources) {
((HasMultipleDOMElementResources) column).freeUnusedResources();
}
}
}
//Then render to the canvas
super.drawWithoutTransforms(context,
alpha,
bb);
}
void destroyDOMElementResources() {
for (GridColumn<?> column : model.getColumns()) {
if (column.getColumnRenderer() instanceof HasDOMElementResources) {
((HasDOMElementResources) column.getColumnRenderer()).destroyResources();
}
}
}
@Override
public Group setVisible(final boolean visible) {
if (!visible) {
for (GridColumn<?> gc : getModel().getColumns()) {
if (gc instanceof HasMultipleDOMElementResources) {
((HasMultipleDOMElementResources) gc).destroyResources();
}
}
}
return super.setVisible(visible);
}
protected void drawHeader(final BaseGridRendererHelper.RenderingInformation renderingInformation,
final boolean isSelectionLayer) {
final List<GridColumn<?>> allColumns = renderingInformation.getAllColumns();
final BaseGridRendererHelper.RenderingBlockInformation bodyBlockInformation = renderingInformation.getBodyBlockInformation();
final BaseGridRendererHelper.RenderingBlockInformation floatingBlockInformation = renderingInformation.getFloatingBlockInformation();
final List<GridColumn<?>> bodyColumns = bodyBlockInformation.getColumns();
final List<GridColumn<?>> floatingColumns = floatingBlockInformation.getColumns();
final double headerX = bodyBlockInformation.getX();
final double headerY = bodyBlockInformation.getHeaderY();
final double floatingHeaderX = floatingBlockInformation.getX();
final double floatingHeaderY = floatingBlockInformation.getHeaderY();
//Add Header, if applicable
final boolean addFixedHeader = renderingInformation.isFixedHeader();
final boolean addFloatingHeader = renderingInformation.isFloatingHeader();
if (addFixedHeader || addFloatingHeader) {
header = renderGridHeaderWidget(allColumns,
bodyColumns,
isSelectionLayer,
renderingInformation);
header.setX(headerX);
if (addFloatingHeader) {
header.setY(headerY);
}
//Draw floating header columns if required
if (floatingColumns.size() > 0) {
floatingHeader = renderGridHeaderWidget(floatingColumns,
floatingColumns,
isSelectionLayer,
renderingInformation);
floatingHeader.setX(floatingHeaderX);
floatingHeader.setY(floatingHeaderY);
}
}
}
protected void drawBody(final BaseGridRendererHelper.RenderingInformation renderingInformation,
final boolean isSelectionLayer) {
final BaseGridRendererHelper.RenderingBlockInformation bodyBlockInformation = renderingInformation.getBodyBlockInformation();
final BaseGridRendererHelper.RenderingBlockInformation floatingBlockInformation = renderingInformation.getFloatingBlockInformation();
final List<GridColumn<?>> bodyColumns = bodyBlockInformation.getColumns();
final List<GridColumn<?>> floatingColumns = floatingBlockInformation.getColumns();
final double bodyX = bodyBlockInformation.getX();
final double bodyY = bodyBlockInformation.getBodyY();
final double floatingBodyX = floatingBlockInformation.getX();
final double floatingBodyY = floatingBlockInformation.getBodyY();
final int minVisibleRowIndex = renderingInformation.getMinVisibleRowIndex();
final int maxVisibleRowIndex = renderingInformation.getMaxVisibleRowIndex();
body = renderGridBodyWidget(bodyColumns,
bodyBlockInformation.getX(),
minVisibleRowIndex,
maxVisibleRowIndex,
isSelectionLayer,
bodyTransformer,
renderingInformation);
body.setX(bodyX);
body.setY(bodyY);
//Include selected ranges of cells
if (!isSelectionLayer) {
body.add(renderSelectedRanges(bodyColumns,
bodyBlockInformation.getX(),
minVisibleRowIndex,
maxVisibleRowIndex,
bodyTransformer,
renderingInformation));
}
//Render floating columns
if (floatingColumns.size() > 0) {
floatingBody = renderGridBodyWidget(floatingColumns,
floatingBlockInformation.getX(),
minVisibleRowIndex,
maxVisibleRowIndex,
isSelectionLayer,
floatingColumnsTransformer,
renderingInformation);
floatingBody.setX(floatingBodyX);
floatingBody.setY(floatingBodyY);
//Include selected ranges of cells
if (!isSelectionLayer) {
floatingBody.add(renderSelectedRanges(floatingColumns,
floatingBlockInformation.getX(),
minVisibleRowIndex,
maxVisibleRowIndex,
floatingColumnsTransformer,
renderingInformation));
}
}
}
/**
* Render the Widget's Header and append to this Group.
* @param allColumns All columns in the model.
* @param blockColumns The columns to render for a block.
* @param isSelectionLayer Is the SelectionLayer being rendered.
*/
protected Group renderGridHeaderWidget(final List<GridColumn<?>> allColumns,
final List<GridColumn<?>> blockColumns,
final boolean isSelectionLayer,
final BaseGridRendererHelper.RenderingInformation renderingInformation) {
final GridHeaderRenderContext context = new GridHeaderRenderContext(allColumns,
blockColumns,
isSelectionLayer);
final Group g = renderer.renderHeader(model,
context,
rendererHelper,
renderingInformation);
return g;
}
/**
* Render the Widget's Body and append to this Group.
* @param blockColumns The columns to render.
* @param absoluteColumnOffsetX Absolute offset from Grid's X co-ordinate to render first column in block.
* @param minVisibleRowIndex The index of the first visible row.
* @param maxVisibleRowIndex The index of the last visible row.
* @param isSelectionLayer Is the SelectionLayer being rendered.
* @param transformer SelectionTransformer in operation.
*/
protected Group renderGridBodyWidget(final List<GridColumn<?>> blockColumns,
final double absoluteColumnOffsetX,
final int minVisibleRowIndex,
final int maxVisibleRowIndex,
final boolean isSelectionLayer,
final SelectionsTransformer transformer,
final BaseGridRendererHelper.RenderingInformation renderingInformation) {
final BaseGridRendererHelper.RenderingBlockInformation floatingBlockInformation = renderingInformation.getFloatingBlockInformation();
final double floatingX = floatingBlockInformation.getX();
final double floatingWidth = floatingBlockInformation.getWidth();
final double clipMinY = getY() + (header == null ? 0.0 : header.getY() + getRenderer().getHeaderHeight());
final double clipMinX = getX() + floatingX + floatingWidth;
final GridBodyRenderContext context = new GridBodyRenderContext(getX(),
getY(),
absoluteColumnOffsetX,
clipMinY,
clipMinX,
minVisibleRowIndex,
maxVisibleRowIndex,
blockColumns,
isSelectionLayer,
getViewport().getTransform(),
renderer,
transformer);
final Group g = renderer.renderBody(model,
context,
rendererHelper,
renderingInformation);
return g;
}
/**
* Render the selected ranges and append to the Body Group.
* @param blockColumns The columns to render.
* @param absoluteColumnOffsetX Absolute offset from Grid's X co-ordinate to render first column in block.
* @param renderingInformation Calculated rendering information supporting rendering.
* @return A Group containing the boundary.
*/
protected Group renderGridBoundary(final List<GridColumn<?>> blockColumns,
final double absoluteColumnOffsetX,
final BaseGridRendererHelper.RenderingInformation renderingInformation) {
final double headerYOffset = (header == null ? 0.0 : header.getY());
final double headerRowsYOffset = renderingInformation.getHeaderRowsYOffset();
final Group boundary = renderer.renderGridBoundary(rendererHelper.getWidth(blockColumns),
getHeight() - headerRowsYOffset - headerYOffset);
boundary.setY(headerRowsYOffset + headerYOffset);
boundary.setX(absoluteColumnOffsetX);
add(boundary);
return boundary;
}
/**
* Render the selected ranges and append to the Body Group.
* @param blockColumns The columns to render.
* @param absoluteColumnOffsetX Absolute offset from Grid's X co-ordinate to render first column in block.
* @param minVisibleRowIndex The index of the first visible row.
* @param maxVisibleRowIndex The index of the last visible row.
* @param transformer SelectionTransformer in operation.
* @return
*/
protected Group renderSelectedRanges(final List<GridColumn<?>> blockColumns,
final double absoluteColumnOffsetX,
final int minVisibleRowIndex,
final int maxVisibleRowIndex,
final SelectionsTransformer transformer,
final BaseGridRendererHelper.RenderingInformation renderingInformation) {
final BaseGridRendererHelper.RenderingBlockInformation floatingBlockInformation = renderingInformation.getFloatingBlockInformation();
final double floatingX = floatingBlockInformation.getX();
final double floatingWidth = floatingBlockInformation.getWidth();
final double clipMinY = getY() + (header == null ? 0.0 : header.getY() + getRenderer().getHeaderHeight());
final double clipMinX = getX() + floatingX + floatingWidth;
final GridBodyRenderContext context = new GridBodyRenderContext(getX(),
getY(),
absoluteColumnOffsetX,
clipMinY,
clipMinX,
minVisibleRowIndex,
maxVisibleRowIndex,
blockColumns,
false,
getViewport().getTransform(),
renderer,
transformer);
final Group g = renderer.renderSelectedCells(model,
context,
rendererHelper);
return g;
}
@Override
public void onNodeMouseClick(final NodeMouseClickEvent event) {
fireEvent(event);
}
@Override
public boolean onGroupingToggle(final double cellX,
final double cellY,
final double cellWidth,
final double cellHeight) {
return renderer.onGroupingToggle(cellX,
cellY,
cellWidth,
cellHeight);
}
@Override
public boolean selectCell(final Point2D ap,
final boolean isShiftKeyDown,
final boolean isControlKeyDown) {
return cellSelectionManager.selectCell(ap,
isShiftKeyDown,
isControlKeyDown);
}
@Override
public boolean selectCell(final int uiRowIndex,
final int uiColumnIndex,
final boolean isShiftKeyDown,
final boolean isControlKeyDown) {
return cellSelectionManager.selectCell(uiRowIndex,
uiColumnIndex,
isShiftKeyDown,
isControlKeyDown);
}
@Override
public boolean adjustSelection(final SelectionExtension direction,
final boolean isShiftKeyDown) {
return cellSelectionManager.adjustSelection(direction,
isShiftKeyDown);
}
@Override
public boolean startEditingCell(final int uiRowIndex,
final int uiColumnIndex) {
return cellSelectionManager.startEditingCell(uiRowIndex,
uiColumnIndex);
}
@Override
public boolean startEditingCell(final Point2D ap) {
return cellSelectionManager.startEditingCell(ap);
}
}