/* * 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.layer.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.ait.lienzo.client.core.event.NodeMouseDownEvent; import com.ait.lienzo.client.core.event.NodeMouseDownHandler; import com.ait.lienzo.client.core.event.NodeMouseMoveEvent; import com.ait.lienzo.client.core.event.NodeMouseUpEvent; import com.ait.lienzo.client.core.shape.IPrimitive; import com.ait.lienzo.client.core.shape.Layer; import com.ait.lienzo.client.core.shape.Line; import com.ait.lienzo.client.core.shape.Viewport; 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 com.ait.lienzo.shared.core.types.ColorName; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.ui.AbsolutePanel; import org.uberfire.ext.wires.core.grids.client.model.Bounds; 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.impl.BaseBounds; import org.uberfire.ext.wires.core.grids.client.widget.dnd.GridWidgetDnDHandlersState; import org.uberfire.ext.wires.core.grids.client.widget.dnd.GridWidgetDnDMouseDownHandler; import org.uberfire.ext.wires.core.grids.client.widget.dnd.GridWidgetDnDMouseMoveHandler; import org.uberfire.ext.wires.core.grids.client.widget.dnd.GridWidgetDnDMouseUpHandler; import org.uberfire.ext.wires.core.grids.client.widget.dom.single.HasSingletonDOMElementResource; import org.uberfire.ext.wires.core.grids.client.widget.grid.GridWidget; import org.uberfire.ext.wires.core.grids.client.widget.grid.animation.GridWidgetScrollIntoViewAnimation; import org.uberfire.ext.wires.core.grids.client.widget.grid.impl.GridWidgetConnector; import org.uberfire.ext.wires.core.grids.client.widget.layer.GridLayer; import org.uberfire.ext.wires.core.grids.client.widget.layer.pinning.TransformMediator; import org.uberfire.ext.wires.core.grids.client.widget.layer.pinning.impl.BoundaryTransformMediator; import org.uberfire.ext.wires.core.grids.client.widget.layer.pinning.impl.DefaultPinnedModeManager; /** * Default implementation of GridLayer */ public class DefaultGridLayer extends Layer implements GridLayer { //This is helpful when debugging rendering issues to set the bounds smaller than the Viewport private static final int PADDING = 0; private final GridWidgetDnDMouseDownHandler mouseDownHandler; private final GridWidgetDnDMouseMoveHandler mouseMoveHandler; private final GridWidgetDnDMouseUpHandler mouseUpHandler; private final GridWidgetDnDHandlersState state = new GridWidgetDnDHandlersState(); private final TransformMediator defaultTransformMediator = new BoundaryTransformMediator(); private final DefaultPinnedModeManager pinnedModeManager = new DefaultPinnedModeManager(this); private Set<GridWidget> gridWidgets = new HashSet<GridWidget>(); private Map<GridWidgetConnector, Line> gridWidgetConnectors = new HashMap<>(); private final GridLayerRedrawManager.PrioritizedCommand REDRAW = new GridLayerRedrawManager.PrioritizedCommand(Integer.MIN_VALUE) { @Override public void execute() { DefaultGridLayer.this.draw(); } }; private AbsolutePanel domElementContainer; private Bounds bounds; public DefaultGridLayer() { this.bounds = new BaseBounds(0, 0, 0, 0); //Column DnD handlers this.mouseDownHandler = new GridWidgetDnDMouseDownHandler(this, state); this.mouseMoveHandler = new GridWidgetDnDMouseMoveHandler(this, state); this.mouseUpHandler = new GridWidgetDnDMouseUpHandler(this, state); addNodeMouseDownHandler(mouseDownHandler); addNodeMouseMoveHandler(mouseMoveHandler); addNodeMouseUpHandler(mouseUpHandler); //Destroy SingletonDOMElements on MouseDownEvents to ensure they're hidden:- // 1) When moving columns // 2) When resizing columns // 3) When the User clicks outside of a GridWidget // We do this rather than setFocus on GridPanel as the FocusImplSafari implementation of // FocusPanel sets focus at unpredictable times which can lead to SingletonDOMElements // loosing focus after they've been attached to the DOM and hence disappearing. addNodeMouseDownHandler(new NodeMouseDownHandler() { @Override public void onNodeMouseDown(final NodeMouseDownEvent event) { for (GridWidget gridWidget : gridWidgets) { for (GridColumn<?> gridColumn : gridWidget.getModel().getColumns()) { if (gridColumn instanceof HasSingletonDOMElementResource) { ((HasSingletonDOMElementResource) gridColumn).flush(); ((HasSingletonDOMElementResource) gridColumn).destroyResources(); batch(); } } } } }); } @Override public void onNodeMouseDown(final NodeMouseDownEvent event) { mouseDownHandler.onNodeMouseDown(event); } @Override public void onNodeMouseMove(final NodeMouseMoveEvent event) { mouseMoveHandler.onNodeMouseMove(event); } @Override public void onNodeMouseUp(final NodeMouseUpEvent event) { mouseUpHandler.onNodeMouseUp(event); } @Override public Layer draw() { //We use Layer.batch() to ensure rendering is tied to the browser's requestAnimationFrame() //however this calls back into Layer.draw() so update dependent Shapes here. updateGridWidgetConnectors(); return super.draw(); } @Override public Layer batch() { return batch(REDRAW); } @Override public Layer batch(final GridLayerRedrawManager.PrioritizedCommand command) { GridLayerRedrawManager.get().schedule(command); return this; } @Override public Set<IPrimitive<?>> getGridWidgetConnectors() { return Collections.unmodifiableSet(new HashSet<>(gridWidgetConnectors.values())); } private void updateGridWidgetConnectors() { for (Map.Entry<GridWidgetConnector, Line> e : gridWidgetConnectors.entrySet()) { final GridWidgetConnector connector = e.getKey(); final Line line = e.getValue(); final GridColumn<?> sourceGridColumn = connector.getSourceColumn(); final GridColumn<?> targetGridColumn = connector.getTargetColumn(); final GridWidget sourceGridWidget = getLinkedGridWidget(sourceGridColumn); final GridWidget targetGridWidget = getLinkedGridWidget(targetGridColumn); final Point2D sp = new Point2D(sourceGridWidget.getX() + sourceGridWidget.getWidth() / 2, sourceGridWidget.getY() + sourceGridWidget.getHeight() / 2); final Point2D ep = new Point2D(targetGridWidget.getX() + targetGridWidget.getWidth() / 2, targetGridWidget.getY() + targetGridWidget.getHeight() / 2); line.setPoints(new Point2DArray(sp, ep)); } } /** * Add a child to this Layer. If the child is a GridWidget then also add * a Connector between the Grid Widget and any "linked" GridWidgets. * @param child Primitive to add to the Layer * @return The Layer */ @Override public Layer add(final IPrimitive<?> child) { addGridWidget(child); return super.add(child); } private void addGridWidget(final IPrimitive<?> child, final IPrimitive<?>... children) { final List<IPrimitive<?>> all = new ArrayList<IPrimitive<?>>(); all.add(child); all.addAll(Arrays.asList(children)); for (IPrimitive<?> c : all) { if (c instanceof GridWidget) { final GridWidget gridWidget = (GridWidget) c; gridWidgets.add(gridWidget); addGridWidgetConnectors(); } } } @Override public void refreshGridWidgetConnectors() { for (Line line : gridWidgetConnectors.values()) { remove(line); } gridWidgetConnectors.clear(); addGridWidgetConnectors(); } private void addGridWidgetConnectors() { for (GridWidget gridWidget : gridWidgets) { final GridData gridModel = gridWidget.getModel(); for (GridColumn<?> gridColumn : gridModel.getColumns()) { if (gridColumn.isVisible()) { if (gridColumn.isLinked()) { final GridWidget linkedGridWidget = getLinkedGridWidget(gridColumn.getLink()); if (linkedGridWidget != null) { final Point2D sp = new Point2D(gridWidget.getX() + gridWidget.getWidth() / 2, gridWidget.getY() + gridWidget.getHeight() / 2); final Point2D ep = new Point2D(linkedGridWidget.getX() + linkedGridWidget.getWidth() / 2, linkedGridWidget.getY() + linkedGridWidget.getHeight() / 2); final GridWidgetConnector connector = new GridWidgetConnector(gridColumn, gridColumn.getLink()); if (!gridWidgetConnectors.containsKey(connector)) { final Line line = new Line(sp, ep) .setVisible(!isGridPinned()) .setStrokeColor(ColorName.DARKGRAY) .setFillColor(ColorName.TAN) .setStrokeWidth(2.0); gridWidgetConnectors.put(connector, line); super.add(line); line.moveToBottom(); } } } } } } } private GridWidget getLinkedGridWidget(final GridColumn<?> linkedGridColumn) { GridWidget linkedGridWidget = null; for (GridWidget gridWidget : gridWidgets) { final GridData gridModel = gridWidget.getModel(); if (gridModel.getColumns().contains(linkedGridColumn)) { linkedGridWidget = gridWidget; break; } } return linkedGridWidget; } /** * Add a child and other children to this Layer. If the child or any children is a GridWidget * then also add a Connector between the Grid Widget and any "linked" GridWidgets. * @param child Primitive to add to the Layer * @param children Additional primitive(s) to add to the Layer * @return The Layer */ @Override public Layer add(final IPrimitive<?> child, final IPrimitive<?>... children) { addGridWidget(child, children); return super.add(child, children); } /** * Remove a child from this Layer. if the child is a GridWidget also remove * any Connectors that have been added between the GridWidget being removed * and any of GridWidgets. * @param child Primitive to remove from the Layer * @return The Layer */ @Override public Layer remove(final IPrimitive<?> child) { removeGridWidget(child); return super.remove(child); } private void removeGridWidget(final IPrimitive<?> child, final IPrimitive<?>... children) { final List<IPrimitive<?>> all = new ArrayList<IPrimitive<?>>(); all.add(child); all.addAll(Arrays.asList(children)); for (IPrimitive<?> c : all) { if (c instanceof GridWidget) { final GridWidget gridWidget = (GridWidget) c; gridWidgets.remove(gridWidget); removeGridWidgetConnectors(gridWidget); } } } private void removeGridWidgetConnectors(final GridWidget gridWidget) { final GridData gridModel = gridWidget.getModel(); final List<GridWidgetConnector> removedConnectors = new ArrayList<GridWidgetConnector>(); for (Map.Entry<GridWidgetConnector, Line> e : gridWidgetConnectors.entrySet()) { if (gridModel.getColumns().contains(e.getKey().getSourceColumn()) || gridModel.getColumns().contains(e.getKey().getTargetColumn())) { remove(e.getValue()); removedConnectors.add(e.getKey()); } } //Remove Connectors from HashMap after iteration of EntrySet to avoid ConcurrentModificationException for (GridWidgetConnector c : removedConnectors) { gridWidgetConnectors.remove(c); } } @Override public Layer removeAll() { gridWidgets.clear(); gridWidgetConnectors.clear(); return super.removeAll(); } @Override public void select(final GridWidget selectedGridWidget) { boolean selectionChanged = false; for (GridWidget gridWidget : gridWidgets) { if (gridWidget.isSelected()) { if (!gridWidget.equals(selectedGridWidget)) { selectionChanged = true; gridWidget.deselect(); } } else if (gridWidget.equals(selectedGridWidget)) { selectionChanged = true; gridWidget.select(); } } if (selectionChanged) { batch(); } } @Override public void selectLinkedColumn(final GridColumn<?> selectedGridColumn) { final GridWidget gridWidget = getLinkedGridWidget(selectedGridColumn); if (gridWidget == null) { return; } if (isGridPinned()) { flipToGridWidget(gridWidget); } else { scrollToGridWidget(gridWidget); } } @Override public void flipToGridWidget(final GridWidget gridWidget) { if (!isGridPinned()) { return; } for (GridWidget gw : gridWidgets) { gw.setAlpha(gw.equals(gridWidget) ? 1.0 : 0.0); gw.setVisible(gw.equals(gridWidget)); } final Point2D translation = new Point2D(gridWidget.getX(), gridWidget.getY()).mul(-1.0); final Viewport vp = gridWidget.getViewport(); final Transform transform = vp.getTransform(); transform.reset(); transform.translate(translation.getX(), translation.getY()); updatePinnedContext(gridWidget); batch(new GridLayerRedrawManager.PrioritizedCommand(0) { @Override public void execute() { select(gridWidget); } }); } @Override public void scrollToGridWidget(final GridWidget gridWidget) { if (isGridPinned()) { return; } final GridWidgetScrollIntoViewAnimation a = new GridWidgetScrollIntoViewAnimation(gridWidget, new Command() { @Override public void execute() { select(gridWidget); } }); a.run(); } @Override public Set<GridWidget> getGridWidgets() { return Collections.unmodifiableSet(gridWidgets); } @Override public void enterPinnedMode(final GridWidget gridWidget, final Command onStartCommand) { pinnedModeManager.enterPinnedMode(gridWidget, onStartCommand); } @Override public void exitPinnedMode(final Command onCompleteCommand) { pinnedModeManager.exitPinnedMode(onCompleteCommand); } @Override public void updatePinnedContext(final GridWidget gridWidget) throws IllegalStateException { pinnedModeManager.updatePinnedContext(gridWidget); } @Override public PinnedContext getPinnedContext() { return pinnedModeManager.getPinnedContext(); } @Override public boolean isGridPinned() { return pinnedModeManager.isGridPinned(); } @Override public TransformMediator getDefaultTransformMediator() { return defaultTransformMediator; } @Override public Bounds getVisibleBounds() { updateVisibleBounds(); return bounds; } private void updateVisibleBounds() { final Viewport viewport = getViewport(); Transform transform = viewport.getTransform(); if (transform == null) { viewport.setTransform(transform = new Transform()); } final double x = (PADDING - transform.getTranslateX()) / transform.getScaleX(); final double y = (PADDING - transform.getTranslateY()) / transform.getScaleY(); bounds.setX(x); bounds.setY(y); bounds.setHeight(Math.max(0, (viewport.getHeight() - PADDING * 2) / transform.getScaleX())); bounds.setWidth(Math.max(0, (viewport.getWidth() - PADDING * 2) / transform.getScaleY())); } @Override public GridWidgetDnDHandlersState getGridWidgetHandlersState() { return state; } @Override public AbsolutePanel getDomElementContainer() { return domElementContainer; } @Override public void setDomElementContainer(final AbsolutePanel domElementContainer) { this.domElementContainer = domElementContainer; } }