/* * Copyright 2009 Fred Sauer * * 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 com.allen_sauer.gwt.dnd.client; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.Widget; import com.allen_sauer.gwt.dnd.client.drop.DropController; import com.allen_sauer.gwt.dnd.client.util.Area; import com.allen_sauer.gwt.dnd.client.util.CoordinateLocation; import com.allen_sauer.gwt.dnd.client.util.DOMUtil; import com.allen_sauer.gwt.dnd.client.util.Location; import com.allen_sauer.gwt.dnd.client.util.WidgetArea; import java.util.ArrayList; import java.util.Arrays; /** * Package private helper implementation class for {@link AbstractDragController} to track all * relevant {@link DropController DropControllers}. */ class DropControllerCollection { protected static class Candidate implements Comparable<Candidate> { private final DropController dropController; private final Area targetArea; Candidate(DropController dropController) { this.dropController = dropController; Widget target = dropController.getDropTarget(); if (!target.isAttached()) { throw new IllegalStateException( "Unattached drop target. You must call DragController#unregisterDropController for all drop targets not attached to the DOM."); } targetArea = new WidgetArea(target, null); } @Override public int compareTo(Candidate other) { Element myElement = getDropTarget().getElement(); Element otherElement = other.getDropTarget().getElement(); return compareElement(myElement, otherElement); } @Override public boolean equals(Object other) { throw new RuntimeException("hash code not implemented"); } @Override public int hashCode() { throw new RuntimeException("hash code not implemented"); } DropController getDropController() { return dropController; } Widget getDropTarget() { return dropController.getDropTarget(); } Area getTargetArea() { return targetArea; } private int compareElement(Element myElement, Element otherElement) { if (myElement == otherElement) { return 0; } else if (DOM.isOrHasChild(myElement, otherElement)) { return -1; } else if (DOM.isOrHasChild(otherElement, myElement)) { return 1; } else { // check parent ensuring global candidate sorting is correct Element myParentElement = myElement.getParentElement().cast(); Element otherParentElement = otherElement.getParentElement().cast(); if (myParentElement != null && otherParentElement != null) { return compareElement(myParentElement, otherParentElement); } return 0; } } } private final ArrayList<DropController> dropControllerList; private Candidate[] sortedCandidates = null; /** * Default constructor. */ DropControllerCollection(ArrayList<DropController> dropControllerList) { this.dropControllerList = dropControllerList; } /** * Determines which DropController represents the deepest DOM descendant drop target located at * the provided location <code>(x, y)</code>. * * @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 */ DropController getIntersectDropController(int x, int y) { Location location = new CoordinateLocation(x, y); if (DOMUtil.DEBUG) { for (int i = sortedCandidates.length - 1; i >= 0; i--) { Candidate candidate = sortedCandidates[i]; DOMUtil.debugWidgetWithColor(candidate.getDropTarget(), "blue"); } } for (int i = sortedCandidates.length - 1; i >= 0; i--) { Candidate candidate = sortedCandidates[i]; Area targetArea = candidate.getTargetArea(); if (targetArea.intersects(location)) { if (DOMUtil.DEBUG) { DOMUtil.debugWidgetWithColor(candidate.getDropTarget(), "green"); } return candidate.getDropController(); } if (DOMUtil.DEBUG) { DOMUtil.debugWidgetWithColor(candidate.getDropTarget(), "red"); } } return null; } /** * Cache a list of eligible drop controllers, sorted by relative DOM positions of their respective * drop targets. Called at the beginning of each drag operation, or whenever drop target * eligibility has changed while dragging. * * @param boundaryPanel boundary area for drop target eligibility considerations * @param context the current drag context */ void resetCache(Panel boundaryPanel, DragContext context) { ArrayList<Candidate> list = new ArrayList<Candidate>(); if (context.draggable != null) { WidgetArea boundaryArea = new WidgetArea(boundaryPanel, null); for (DropController dropController : dropControllerList) { Candidate candidate = new Candidate(dropController); Widget dropTarget = candidate.getDropTarget(); if (DOM.isOrHasChild(context.draggable.getElement(), dropTarget.getElement())) { continue; } if (candidate.getTargetArea().intersects(boundaryArea)) { list.add(candidate); } } } sortedCandidates = list.toArray(new Candidate[list.size()]); Arrays.sort(sortedCandidates); } }