/*
* Copyright 2010 The gwtquery plugins team.
*
* 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 gwtquery.plugins.draggable.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.GQuery.Offset;
import com.google.gwt.query.client.Properties;
import com.google.gwt.query.client.plugins.Effects;
import com.google.gwt.query.client.plugins.UiPlugin;
import com.google.gwt.query.client.plugins.UiPlugin.Dimension;
import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing;
import com.google.gwt.query.client.plugins.events.GqEvent;
import com.google.gwt.user.client.Window;
import gwtquery.plugins.draggable.client.Draggable.CssClassNames;
import gwtquery.plugins.draggable.client.DraggableOptions.AxisOption;
import gwtquery.plugins.draggable.client.DraggableOptions.CursorAt;
import gwtquery.plugins.draggable.client.DraggableOptions.HelperType;
import gwtquery.plugins.draggable.client.impl.DraggableHandlerImpl;
import static com.google.gwt.query.client.GQuery.$;
import static com.google.gwt.query.client.GQuery.body;
import static gwtquery.plugins.draggable.client.Draggable.DRAGGABLE_HANDLER_KEY;
public class DraggableHandler {
public static DraggableHandler getInstance(Element draggable) {
return $(draggable).data(DRAGGABLE_HANDLER_KEY, DraggableHandler.class);
}
private DraggableHandlerImpl impl = GWT.create(DraggableHandlerImpl.class);
private Offset margin;
private Offset offset;
private Offset absPosition;
// from where the click happened relative to the draggable element
private Offset offsetClick;
private Offset parentOffset;
private Offset relativeOffset;
private int originalEventPageX;
private int originalEventPageY;
private Offset position;
private Offset originalPosition;
// info from helper
private String helperCssPosition;
private GQuery helperScrollParent;
private GQuery helperOffsetParent;
private int[] containment;
private GQuery helper;
private DraggableOptions options;
private Dimension helperDimension;
private boolean cancelHelperRemoval = false;
// can be instantiate only by Draggable plugin
DraggableHandler(DraggableOptions options) {
this.options = options;
}
/**
* convert a relative position to a absolute position and vice versa.
*
* @param absolute if true the position is convert to an absolute position, if
* false it is convert in a relative position
* @param aPosition position to convert
* @return
*/
public Offset convertPositionTo(boolean absolute, Offset aPosition) {
int mod = absolute ? 1 : -1;
GQuery scroll = getScrollParent();
boolean scrollIsRootNode = isRootNode(scroll.get(0));
int top = aPosition.top
+ relativeOffset.top
* mod
+ parentOffset.top
* mod
- ("fixed".equals(helperCssPosition) ? -helperScrollParent.scrollTop()
: scrollIsRootNode ? 0 : scroll.scrollTop()) * mod;
int left = aPosition.left
+ relativeOffset.left
* mod
+ parentOffset.left
* mod
- ("fixed".equals(helperCssPosition) ? -helperScrollParent.scrollLeft()
: scrollIsRootNode ? 0 : scroll.scrollLeft()) * mod;
return new Offset(left, top);
}
public int[] getContainment() {
return containment;
}
public GQuery getHelper() {
return helper;
}
public String getHelperCssPosition() {
return helperCssPosition;
}
public Dimension getHelperDimension() {
return helperDimension;
}
public GQuery getHelperOffsetParent() {
return helperOffsetParent;
}
public GQuery getHelperScrollParent() {
return helperScrollParent;
}
public Offset getMargin() {
return margin;
}
public Offset getOffset() {
return offset;
}
public Offset getOffsetClick() {
return offsetClick;
}
public DraggableOptions getOptions() {
return options;
}
public int getOriginalEventPageX() {
return originalEventPageX;
}
public int getOriginalEventPageY() {
return originalEventPageY;
}
public Offset getOriginalPosition() {
return originalPosition;
}
public Offset getParentOffset() {
return parentOffset;
}
public Offset getPosition() {
return position;
}
public Offset getAbsolutePosition() {
return absPosition;
}
public Offset getRelativeOffset() {
return relativeOffset;
}
public void initialize(Element element, GqEvent e) {
helperCssPosition = helper.css("position");
helperScrollParent = helper.as(UiPlugin.GQueryUi).scrollParent();
helperOffsetParent = helper.offsetParent();
if ("html".equalsIgnoreCase(helperOffsetParent.get(0).getTagName())) {
helperOffsetParent = $(body);
}
setMarginCache(element);
absPosition = new Offset(element.getAbsoluteLeft(),
element.getAbsoluteTop());
offset = new Offset(absPosition.left - margin.left, absPosition.top
- margin.top);
offsetClick = new Offset(e.pageX() - offset.left, e.pageY() - offset.top);
parentOffset = calculateParentOffset(element);
relativeOffset = calculateRelativeHelperOffset(element);
originalEventPageX = e.pageX();
originalEventPageY = e.pageY();
position = calculateOriginalPosition(element, e);
originalPosition = new Offset(position.left, position.top);
if (options.getCursorAt() != null) {
adjustOffsetFromHelper(options.getCursorAt());
}
calculateContainment();
}
private Offset calculateOriginalPosition(Element element, GqEvent e) {
if (HelperType.ORIGINAL == options.getHelperType()) {
return impl.getCssPosition(element);
} else {
return generatePosition(e, true);
}
}
public boolean isRootNode(Element e) {
return "html".equalsIgnoreCase(e.getTagName()) || e == body;
}
/**
* @param firstTime if true, the helper has to be positionned without take
* care to the axis options
*/
public void moveHelper(boolean firstTime) {
if (helper == null || helper.size() == 0) {
return;
}
AxisOption axis = options.getAxis();
if (AxisOption.NONE == axis || AxisOption.X_AXIS == axis || firstTime) {
helper.get(0).getStyle().setLeft(position.left, Unit.PX);
}
if (AxisOption.NONE == axis || AxisOption.Y_AXIS == axis || firstTime) {
helper.get(0).getStyle().setTop(position.top, Unit.PX);
}
}
public void regeneratePositions(GqEvent e) {
position = generatePosition(e, false);
offset = convertPositionTo(true, position);
absPosition = offset.add(margin.left, margin.top);
}
public void revertToOriginalPosition(Function function) {
Properties oldPosition = Properties.create("{top:'"
+ String.valueOf(originalPosition.top) + "px',left:'"
+ String.valueOf(originalPosition.left) + "px'}");
helper.as(Effects.Effects).animate(oldPosition,
options.getRevertDuration(), Easing.LINEAR, function);
}
public void setHelperDimension(Dimension helperDimension) {
this.helperDimension = helperDimension;
}
public void setMarginCache(Element element) {
int marginLeft = (int) GQuery.$(element).cur("marginLeft", true);
int marginTop = (int) GQuery.$(element).cur("marginTop", true);
margin = new Offset(marginLeft, marginTop);
}
public void setOptions(DraggableOptions options) {
this.options = options;
}
public void setPosition(Offset Offset) {
position = Offset;
}
public void setOffsetClick(Offset offsetClick) {
this.offsetClick = offsetClick;
}
void cacheHelperSize() {
if (helper != null) {
setHelperDimension(new Dimension(helper.get(0)));
}
}
void clear(Element draggable) {
if (helper == null) {
return;
}
helper.removeClass(CssClassNames.GWT_DRAGGABLE_DRAGGING);
if (HelperType.ORIGINAL != options.getHelperType() && !cancelHelperRemoval) {
impl.removeHelper(helper, options.getHelperType());
}
helper = null;
cancelHelperRemoval = false;
}
void createHelper(Element draggable, GqEvent e) {
helper = options.getHelperType().createHelper(draggable,
options.getHelper());
if (!isElementAttached(helper)) {
if ("parent".equals(options.getAppendTo())) {
helper.appendTo(draggable.getParentNode());
} else {
helper.appendTo(options.getAppendTo());
}
}
if (options.getHelperType() != HelperType.ORIGINAL
&& !helper.css("position").matches("(fixed|absolute)")) {
helper.css("position", Position.ABSOLUTE.getCssName());
}
}
private void adjustOffsetFromHelper(CursorAt cursorAt) {
if (cursorAt.getLeft() != null) {
offsetClick.left = cursorAt.getLeft().intValue() + margin.left;
}
if (cursorAt.getRight() != null) {
offsetClick.left = helperDimension.getWidth()
- cursorAt.getRight().intValue() + margin.left;
}
if (cursorAt.getTop() != null) {
offsetClick.top = cursorAt.getTop().intValue() + margin.top;
}
if (cursorAt.getBottom() != null) {
offsetClick.top = helperDimension.getHeight()
- cursorAt.getBottom().intValue() + margin.top;
}
}
private void calculateContainment() {
String containmentAsString = options.getContainment();
int[] containmentAsArray = options.getContainmentAsArray();
GQuery $containement = options.getContainmentAsGQuery();
if (containmentAsArray == null && containmentAsString == null
&& $containement == null) {
containment = null;
return;
}
if (containmentAsArray != null) {
containment = containmentAsArray;
return;
}
if (containmentAsString != null) {
if ("window".equals(containmentAsString)) {
containment = new int[]{
0 /*- relativeOffset.left - parentOffset.left*/,
0 /*- relativeOffset.top - parentOffset.top*/,
Window.getClientWidth() - helperDimension.getWidth() - margin.left,
Window.getClientHeight() - helperDimension.getHeight() - margin.top};
return;
}
if ("parent".equals(containmentAsString)) {
$containement = $(helper.get(0).getParentElement());
} else if ("document".equals(containmentAsString)) {
$containement = $("body");
} else {
$containement = $(containmentAsString);
}
}
Element ce = $containement.get(0);
if (ce == null) {
return;
}
containment = impl.calculateContainment(this, $containement.offset(), ce,
(!"hidden".equals($containement.css("overflow"))));
}
private Offset calculateParentOffset(Element element) {
Offset position = helperOffsetParent.offset();
if ("absolute".equals(helperCssPosition)
&& isOffsetParentIncludedInScrollParent()) {
position = position.add(helperScrollParent.scrollLeft(),
helperScrollParent.scrollTop());
}
if (impl.resetParentOffsetPosition(helperOffsetParent)) {
position.left = 0;
position.top = 0;
}
position = position.add((int) helperOffsetParent.cur("borderLeftWidth",
true), (int) helperOffsetParent.cur("borderTopWidth", true));
return new Offset(position.left, position.top);
}
/*
* This is a relative to absolute position minus the actual position
* calculation - only used for relative positioned helper
*/
private Offset calculateRelativeHelperOffset(Element element) {
if ("relative".equals(helperCssPosition)) {
return impl.calculateRelativeHelperOffset(element, this);
}
return new Offset(0, 0);
}
private Offset generatePosition(GqEvent e, boolean initPosition) {
GQuery scroll = getScrollParent();
boolean scrollIsRootNode = isRootNode(scroll.get(0));
int pageX = e.pageX();
int pageY = e.pageY();
if (!initPosition) {
if (containment != null && containment.length == 4) {
if (e.pageX() - offsetClick.left < containment[0]) {
pageX = containment[0] + offsetClick.left;
}
if (e.pageY() - offsetClick.top < containment[1]) {
pageY = containment[1] + offsetClick.top;
}
if (e.pageX() - offsetClick.left > containment[2]) {
pageX = containment[2] + offsetClick.left;
}
if (e.pageY() - offsetClick.top > containment[3]) {
pageY = containment[3] + offsetClick.top;
}
}
if (options.getGrid() != null) {
int[] grid = options.getGrid();
int roundedTop = originalEventPageY
+ Math.round((pageY - originalEventPageY) / grid[1]) * grid[1];
int roundedLeft = originalEventPageX
+ Math.round((pageX - originalEventPageX) / grid[0]) * grid[0];
if (containment != null && containment.length == 4) {
boolean isOutOfContainment0 = roundedLeft - offsetClick.left < containment[0];
boolean isOutOfContainment1 = roundedTop - offsetClick.top < containment[1];
boolean isOutOfContainment2 = roundedLeft - offsetClick.left > containment[2];
boolean isOutOfContainment3 = roundedTop - offsetClick.top > containment[3];
pageY = !(isOutOfContainment1 || isOutOfContainment3) ? roundedTop
: (!isOutOfContainment1) ? roundedTop - grid[1] : roundedTop
+ grid[1];
pageX = !(isOutOfContainment0 || isOutOfContainment2) ? roundedLeft
: (!isOutOfContainment0) ? roundedLeft - grid[0] : roundedLeft
+ grid[0];
} else {
pageY = roundedTop;
pageX = roundedLeft;
}
}
}
int top = pageY
- offsetClick.top
- relativeOffset.top
- parentOffset.top
+ ("fixed".equals(helperCssPosition) ? -helperScrollParent.scrollTop()
: scrollIsRootNode ? 0 : scroll.scrollTop());
int left = pageX
- offsetClick.left
- relativeOffset.left
- parentOffset.left
+ ("fixed".equals(helperCssPosition) ? -helperScrollParent.scrollLeft()
: scrollIsRootNode ? 0 : scroll.scrollLeft());
return new Offset(left, top);
}
private GQuery getScrollParent() {
if ("absolute".equals(helperCssPosition)
&& !(isOffsetParentIncludedInScrollParent())) {
return helperOffsetParent;
} else {
return helperScrollParent;
}
}
private boolean isElementAttached(GQuery $element) {
// normally this test helper.parents().filter("body").length() == 0 is
// sufficient but they are a bug in gwtquery in filter function
// return helper.parents().filter("body").length() == 0;
GQuery parents = $element.parents();
for (Element parent : parents.elements()) {
if (parent == body) {
return true;
}
}
return false;
}
private boolean isOffsetParentIncludedInScrollParent() {
assert helperOffsetParent != null && helperScrollParent != null;
return !"html".equalsIgnoreCase(helperScrollParent.get(0).getTagName())
&& UiPlugin.contains(helperScrollParent.get(0),
helperOffsetParent.get(0));
}
}