Package org.thechiselgroup.choosel.dnd.client.resources

Source Code of org.thechiselgroup.choosel.dnd.client.resources.DefaultResourceSetAvatarDragController

/*******************************************************************************
* Copyright 2009, 2010 Lars Grammel
*
* 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.thechiselgroup.choosel.dnd.client.resources;

import static org.thechiselgroup.choosel.core.client.configuration.ChooselInjectionConstants.ROOT_PANEL;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.thechiselgroup.choosel.core.client.error_handling.ErrorHandler;
import org.thechiselgroup.choosel.core.client.geometry.Rectangle;
import org.thechiselgroup.choosel.core.client.resources.ui.ResourceSetAvatar;
import org.thechiselgroup.choosel.core.client.ui.CSS;
import org.thechiselgroup.choosel.core.client.ui.shade.ShadeManager;
import org.thechiselgroup.choosel.core.client.util.RemoveHandle;
import org.thechiselgroup.choosel.core.client.util.math.MathUtils;
import org.thechiselgroup.choosel.core.client.visualization.model.extensions.HighlightingModel;
import org.thechiselgroup.choosel.dnd.client.windows.Desktop;
import org.thechiselgroup.choosel.dnd.client.windows.WindowPanel;

import com.allen_sauer.gwt.dnd.client.AbstractDragController;
import com.allen_sauer.gwt.dnd.client.DragContext;
import com.allen_sauer.gwt.dnd.client.VetoDragException;
import com.allen_sauer.gwt.dnd.client.drop.BoundaryDropController;
import com.allen_sauer.gwt.dnd.client.drop.DropController;
import com.allen_sauer.gwt.dnd.client.util.DOMUtil;
import com.allen_sauer.gwt.dnd.client.util.DragClientBundle;
import com.allen_sauer.gwt.dnd.client.util.Location;
import com.allen_sauer.gwt.dnd.client.util.WidgetLocation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.name.Named;

// TODO move area calculation to preview package & use delegation instead
// (composition when creating drag avatar drag controller while setting
// up system)
/*
* This class is not safe for multiple simultaneous drags (e.g. multi touch interfaces)
*/
public class DefaultResourceSetAvatarDragController extends
        AbstractDragController implements ResourceSetAvatarDragController {

    /**
     * TODO Decide if 100ms is a good number
     */
    private final static int CACHE_TIME_MILLIS = 100;

    private static final String CSS_SHADE_CLASS = "shade";

    // TODO document // move??
    private static final int CSS_SHADE_Z_INDEX = 1;

    private static void checkGWTIssue1813(Widget child, AbsolutePanel parent) {
        if (!GWT.isScript()) {
            if (child.getElement().getOffsetParent() != parent.getElement()) {
                DOMUtil.reportFatalAndThrowRuntimeException("The boundary panel for this drag controller does not appear to have"
                        + " 'position: relative' CSS applied to it."
                        + " This may be due to custom CSS in your application, although this"
                        + " is often caused by using the result of RootPanel.get(\"some-unique-id\") as your boundary"
                        + " panel, as described in GWT issue 1813"
                        + " (http://code.google.com/p/google-web-toolkit/issues/detail?id=1813)."
                        + " Please star / vote for this issue if it has just affected your application."
                        + " You can often remedy this problem by adding one line of code to your application:"
                        + " boundaryPanel.getElement().getStyle().setProperty(\"position\", \"relative\");");
            }
        }
    }

    private static List<Rectangle> toRectangles(List<Area> areas) {
        List<Rectangle> rectangles = new ArrayList<Rectangle>();
        for (Area dropArea : areas) {
            rectangles.add(dropArea.getRectangle());
        }
        return rectangles;
    }

    /**
     * The implicit boundary drop controller.
     */
    private BoundaryDropController boundaryDropController;

    private Rectangle boundaryRectangle = new Rectangle(0, 0, 0, 0);

    private Desktop desktop;

    private ResourceSetAvatar dragProxy;

    private Map<Widget, ResourceSetAvatarDropController> dropControllers = new HashMap<Widget, ResourceSetAvatarDropController>();

    private long lastResetCacheTimeMillis;

    /**
     * shade background elements for drop targets with rounded corners
     */
    private List<Element> shadeElements = new ArrayList<Element>();

    private ShadeManager shadeManager;

    private RemoveHandle shadeRemoveHandle;

    private List<Widget> temporaryDropTargets = new ArrayList<Widget>();

    private List<Area> visibleDropAreas;

    private HighlightingModel hoverModel;

    private ErrorHandler errorHandler;

    @Inject
    public DefaultResourceSetAvatarDragController(
            @Named(ROOT_PANEL) AbsolutePanel panel, Desktop desktop,
            ShadeManager shadeManager, HighlightingModel hoverModel,
            ErrorHandler errorHandler) {

        super(panel);

        assert shadeManager != null;
        assert hoverModel != null;
        assert errorHandler != null;

        this.hoverModel = hoverModel;
        this.shadeManager = shadeManager;
        this.desktop = desktop;
        this.errorHandler = errorHandler;

        this.boundaryDropController = new BoundaryDropController(panel, false);

        setBehaviorDragStartSensitivity(2);
    }

    private void addShade() {
        List<Rectangle> visibleRectangles = toRectangles(visibleDropAreas);
        shadeRemoveHandle = shadeManager.showShade(visibleRectangles);

        insertShadeBehindRoundedDropTargets();
    }

    /*
     * This code is from the dnd library and needs TODO refactoring. There are
     * several possible side effects that need to be checked.
     */
    private void calculateBoundaryOffset() {
        assert context.boundaryPanel == getBoundaryPanel();

        AbsolutePanel boundaryPanel = getBoundaryPanel();
        Location widgetLocation = new WidgetLocation(boundaryPanel, null);
        Element boundaryElement = boundaryPanel.getElement();

        int left = widgetLocation.getLeft()
                + DOMUtil.getBorderLeft(boundaryElement);
        int top = widgetLocation.getTop()
                + DOMUtil.getBorderTop(boundaryElement);
        boundaryRectangle = boundaryRectangle.move(left, top);

    }

    private void calculateBoundaryParameters() {
        calculateBoundaryOffset();

        Element element = getBoundaryPanel().getElement();

        int width = DOMUtil.getClientWidth(element);
        int height = DOMUtil.getClientHeight(element);
        boundaryRectangle = boundaryRectangle.resize(width, height);
    }

    // TODO change to calculate visible areas with reference to drop controller
    private void calculateDropAreas() {
        List<Area> windowAreas = getWindowAreas();
        List<Area> dropTargetAreas = getDropTargetAreas();

        List<Area> dropAreas = new ArrayList<Area>();
        for (Area dropArea : dropTargetAreas) {
            dropAreas.addAll(dropArea.getVisibleParts(windowAreas));
        }

        visibleDropAreas = dropAreas;
    }

    /**
     * Calculates which of the hidden drop targets are relevant to this drop
     * operation and stores them as temporary drop targets.
     */
    private void calculateTemporaryDropTargets() {
        for (ResourceSetAvatarDropController dropController : getAvailableDropControllers()) {
            Widget dropTarget = dropController.getDropTarget();
            if (!dropTarget.isVisible()) {
                temporaryDropTargets.add(dropTarget);
            }
        }
    }

    private boolean canDropOn(ResourceSetAvatarDropController dropController) {
        return dropController.canDrop(context);
    }

    private void clearDropAreas() {
        visibleDropAreas = null;
    }

    private void clearTemporaryDropTargets() {
        temporaryDropTargets.clear();
    }

    /**
     * Creates a shade element that has bounds of drop target element, but an
     * absolute location, and is positioned behind the drop target.
     */
    private Element createShadeElement(Widget dropTarget) {
        Element shade = DOM.createSpan();

        shade.addClassName(CSS_SHADE_CLASS);

        CSS.setZIndex(shade, CSS_SHADE_Z_INDEX);

        WindowPanel window = getWindow(dropTarget);
        CSS.setAbsoluteBounds(shade,
                dropTarget.getAbsoluteLeft() - window.getAbsoluteLeft(),
                dropTarget.getAbsoluteTop() - window.getAbsoluteTop(),
                dropTarget.getOffsetWidth(), dropTarget.getOffsetHeight());

        return shade;
    }

    @Override
    public void dragEnd() {
        setResourceSetHighlighted(false);
        removeShade();
        setTemporaryDropTargetsVisible(false);
        clearTemporaryDropTargets();
        clearDropAreas();
        notifyDropControllerOnDragEnd();
        removeDragProxy();

        super.dragEnd();
    }

    @Override
    public void dragMove() {
        updateCacheAndBoundary();
        moveDragProxy();
        updateDropController();
    }

    @Override
    public void dragStart() {
        super.dragStart();

        updateLastCacheResetTime();
        initDragProxy();
        calculateBoundaryParameters();
        calculateTemporaryDropTargets();
        setTemporaryDropTargetsVisible(true);
        calculateDropAreas();
        addShade();
        setResourceSetHighlighted(true);
    }

    private List<ResourceSetAvatarDropController> getAvailableDropControllers() {
        List<ResourceSetAvatarDropController> availableControllers = new ArrayList<ResourceSetAvatarDropController>();
        for (ResourceSetAvatarDropController dropController : dropControllers
                .values()) {

            /*
             * We use an error handler to make sure exception in the drop
             * controllers are recorded, but do not affect the overall drag
             * operation.
             */
            try {
                if (canDropOn(dropController)) {
                    availableControllers.add(dropController);
                }
            } catch (Throwable ex) {
                errorHandler.handleError(ex);
            }
        }
        return availableControllers;
    }

    private DraggableResourceSetAvatar getAvatar(DragContext context) {
        assert context != null;
        assert context.draggable != null;
        assert context.draggable instanceof DraggableResourceSetAvatar : "context.draggable is not of type DraggableResourceSetAvatar";
        return (DraggableResourceSetAvatar) context.draggable;
    }

    private int getDesiredLeft() {
        int desiredLeft = context.desiredDraggableX - boundaryRectangle.getX();
        if (getBehaviorConstrainedToBoundaryPanel()) {
            desiredLeft = MathUtils.restrictToInterval(
                    boundaryRectangle.getWidth()
                            - context.draggable.getOffsetWidth(), 0,
                    desiredLeft);
        }
        return desiredLeft;
    }

    private int getDesiredTop() {
        int desiredTop = context.desiredDraggableY - boundaryRectangle.getY();
        if (getBehaviorConstrainedToBoundaryPanel()) {
            desiredTop = MathUtils.restrictToInterval(
                    boundaryRectangle.getHeight()
                            - context.draggable.getOffsetHeight(), 0,
                    desiredTop);
        }
        return desiredTop;
    }

    /**
     * @param x
     *            offset left relative to document body
     * @param y
     *            offset top relative to document body
     * @return a drop controller for the intersecting drop target or
     *         <code>null</code> if none are applicable
     */
    private DropController getDropControllerForLocation(int x, int y) {
        // our rectangles/areas have absolute offsets so we are good
        // since we already calculated the visible ones, we don't need ordering

        for (Area area : visibleDropAreas) {
            if (area.getRectangle().contains(x, y)) {
                return area.getDropController();
            }
        }

        return boundaryDropController;
    }

    private List<Area> getDropTargetAreas() {
        List<Area> areas = new ArrayList<Area>();
        for (ResourceSetAvatarDropController dropController : getAvailableDropControllers()) {
            Widget dropTarget = dropController.getDropTarget();
            Rectangle rectangle = Rectangle.fromWidget(dropTarget);
            WindowPanel window = getWindow(dropTarget);

            areas.add(new Area(rectangle, window, dropController));
        }
        return areas;
    }

    private WindowPanel getWindow(Widget originalWidget) {
        assert originalWidget != null;

        Widget widget = originalWidget;
        while (widget != null) {
            if (widget instanceof WindowPanel) {
                return (WindowPanel) widget;
            }
            widget = widget.getParent();
        }

        throw new RuntimeException("no window found for widget "
                + originalWidget);
    }

    private List<Area> getWindowAreas() {
        List<Area> windowAreas = new ArrayList<Area>();
        List<WindowPanel> windows = desktop.getWindows();
        for (WindowPanel window : windows) {
            Rectangle r = Rectangle.fromWidget(window);
            windowAreas.add(new Area(r, window, null));
        }
        return windowAreas;
    }

    // TODO might need a more generic implementation at some point
    private boolean hasRoundedCorners(Widget dropTarget) {
        return dropTarget instanceof ResourceSetAvatar;
    }

    /**
     * Creates the visual representation of the object that is being dragged.
     * This representation is used to indicate what is being dragged to the user
     * during the drag operation.
     */
    private void initDragProxy() {
        WidgetLocation currentDraggableLocation = new WidgetLocation(
                context.draggable, context.boundaryPanel);

        dragProxy = getAvatar(context).createProxy();
        dragProxy.setHover(true);

        context.boundaryPanel.add(dragProxy,
                currentDraggableLocation.getLeft(),
                currentDraggableLocation.getTop());
        checkGWTIssue1813(dragProxy, context.boundaryPanel);
        dragProxy.addStyleName(DragClientBundle.INSTANCE.css().movablePanel());
    }

    /**
     * Inserts a shade element behind resource set avatars that are available
     * drop targets, because they have rounded corners and the area outside of
     * those corners needs to be shaded.
     */
    private void insertShadeBehindRoundedDropTargets() {
        for (ResourceSetAvatarDropController dropController : getAvailableDropControllers()) {
            Widget dropTarget = dropController.getDropTarget();
            if (hasRoundedCorners(dropTarget)) {
                Element shade = createShadeElement(dropTarget);

                ((Element) dropTarget.getElement().getParentNode())
                        .appendChild(shade);
                shadeElements.add(shade);
            }
        }
    }

    private void moveDragProxy() {
        Style style = dragProxy.getElement().getStyle();

        style.setPropertyPx(CSS.LEFT, getDesiredLeft());
        style.setPropertyPx(CSS.TOP, getDesiredTop());
    }

    private void notifyDropControllerOnDragEnd() {
        assert (context.finalDropController == null) != (context.vetoException == null);
        if (context.vetoException == null) {
            context.dropController.onDrop(context);
            context.dropController.onLeave(context);
            context.dropController = null;
        }
    }

    // copied from PickupDragController
    @Override
    public void previewDragEnd() throws VetoDragException {
        assert context.finalDropController == null;
        assert context.vetoException == null;
        try {
            try {
                // may throw VetoDragException
                context.dropController.onPreviewDrop(context);
                context.finalDropController = context.dropController;
            } finally {
                // may throw VetoDragException
                super.previewDragEnd();
            }
        } catch (VetoDragException ex) {
            context.finalDropController = null;
            throw ex;
        }
    }

    @Override
    public void registerDropController(
            ResourceSetAvatarDropController dropController) {
        dropControllers.put(dropController.getDropTarget(), dropController);
    }

    private void removeDragProxy() {
        dragProxy.removeFromParent();
        dragProxy = null;
    }

    private void removeShade() {
        for (Element shadeElement : shadeElements) {
            shadeElement.removeFromParent();
        }
        shadeElements.clear();
        shadeRemoveHandle.remove();
    }

    @Override
    public void setDraggable(Widget widget, boolean draggable) {
        if (draggable) {
            makeDraggable(widget);
        } else {
            makeNotDraggable(widget);
        }
    }

    private void setResourceSetHighlighted(boolean highlighted) {
        hoverModel.setHighlightedResourceSet(highlighted ? getAvatar(context)
                .getResourceSet() : null);
    }

    private void setTemporaryDropTargetsVisible(boolean visible) {
        for (Widget w : temporaryDropTargets) {
            w.setVisible(visible);
        }
    }

    @Override
    public void unregisterDropController(
            ResourceSetAvatarDropController dropController) {

        assert dropController != null;

        dropControllers.remove(dropController.getDropTarget());
    }

    @Override
    public void unregisterDropControllerFor(Widget dropTarget) {
        unregisterDropController(dropControllers.get(dropTarget));
    }

    private void updateCacheAndBoundary() {
        // may have changed due to scrollIntoView(), developer driven changes
        // or manual user scrolling
        if (System.currentTimeMillis() - lastResetCacheTimeMillis >= CACHE_TIME_MILLIS) {
            updateLastCacheResetTime();
            resetCache();
            calculateBoundaryOffset();
        }
    }

    private void updateDropController() {
        try {
            DropController newDropController = getDropControllerForLocation(
                    context.mouseX, context.mouseY);

            if (context.dropController != newDropController) {
                if (context.dropController != null) {
                    context.dropController.onLeave(context);
                }
                context.dropController = newDropController;
                if (context.dropController != null) {
                    context.dropController.onEnter(context);
                }
            }

            if (context.dropController != null) {
                context.dropController.onMove(context);
            }
        } catch (Exception ex) {
            // abort drop operation and show dialog if exception occurs
            // TODO abort drop operation

            errorHandler.handleError(ex);
        }
    }

    private void updateLastCacheResetTime() {
        lastResetCacheTimeMillis = System.currentTimeMillis();
    }

}
TOP

Related Classes of org.thechiselgroup.choosel.dnd.client.resources.DefaultResourceSetAvatarDragController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.