/* * Copyright 2011 John Ahlroos * * 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 fi.jasoft.dragdroplayouts.client.ui; import java.util.Iterator; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.Paintable; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.ui.VCssLayout; import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation; import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback; import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent; import com.vaadin.terminal.gwt.client.ui.dd.VDropHandler; import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler; import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; import fi.jasoft.dragdroplayouts.DDCssLayout; import fi.jasoft.dragdroplayouts.client.ui.VLayoutDragDropMouseHandler.DragStartListener; import fi.jasoft.dragdroplayouts.client.ui.interfaces.VHasDragMode; import fi.jasoft.dragdroplayouts.client.ui.util.IframeCoverUtility; /** * Client side implementation for {@link DDCssLayout} * * @author John Ahlroos / www.jasoft.fi * @since 0.7.0 * */ public class VDDCssLayout extends VCssLayout implements VHasDragMode, VHasDropHandler, DragStartListener { public static final float DEFAULT_HORIZONTAL_DROP_RATIO = 0.2f; public static final float DEFAULT_VERTICAL_DROP_RATIO = 0.2f; public static final String DRAG_SHADOW_STYLE_NAME = "v-ddcsslayout-drag-shadow"; private LayoutDragMode dragMode = LayoutDragMode.NONE; private final VDragFilter dragFilter = new VDragFilter(); private VAbstractDropHandler dropHandler; private final VLayoutDragDropMouseHandler ddHandler = new VLayoutDragDropMouseHandler( this, dragMode); protected ApplicationConnection client; protected boolean iframeCoversEnabled = false; private final IframeCoverUtility iframeCoverUtility = new IframeCoverUtility(); private float horizontalDropRatio = DEFAULT_HORIZONTAL_DROP_RATIO; private float verticalDropRatio = DEFAULT_VERTICAL_DROP_RATIO; /** * Default constructor */ public VDDCssLayout() { super(); ddHandler.addDragStartListener(this); } /** * Can be used to listen to drag start events, must return true for the drag * to commence. Return false to interrupt the drag: */ public boolean dragStart(Widget widget, LayoutDragMode mode) { return dragMode != LayoutDragMode.NONE && dragFilter.isDraggable(widget); } /** * Returns the drop handler which handles the drop events */ public VDropHandler getDropHandler() { return dropHandler; } /** * Returns the drag mode * * @return */ public LayoutDragMode getDragMode() { return dragMode; } /** * Updates the drop handler. Creates a drop handler if it does not exist. * * @param childUidl * The child UIDL containing the rules */ protected void updateDropHandler(UIDL childUidl) { if (dropHandler == null) { dropHandler = new VAbstractDropHandler() { /* * (non-Javadoc) * * @see com.vaadin.terminal.gwt.client.ui.dd.VDropHandler# * getApplicationConnection() */ public ApplicationConnection getApplicationConnection() { return client; } /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler * #dragAccepted * (com.vaadin.terminal.gwt.client.ui.dd.VDragEvent) */ @Override protected void dragAccepted(VDragEvent drag) { // Intentionally left empty } /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler * #getPaintable() */ @Override public Paintable getPaintable() { return VDDCssLayout.this; } /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler * #drop(com.vaadin.terminal.gwt.client.ui.dd.VDragEvent) */ @Override public boolean drop(VDragEvent drag) { updateDragDetails(drag); detachDragImageFromLayout(drag); return postDropHook(drag) && super.drop(drag); }; /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler * #dragEnter(com.vaadin.terminal.gwt.client.ui.dd.VDragEvent) */ @Override public void dragEnter(VDragEvent drag) { super.dragEnter(drag); attachDragImageToLayout(drag); updateDragDetails(drag); postEnterHook(drag); }; /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler * #dragLeave(com.vaadin.terminal.gwt.client.ui.dd.VDragEvent) */ @Override public void dragLeave(VDragEvent drag) { super.dragLeave(drag); detachDragImageFromLayout(drag); postLeaveHook(drag); }; /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler * #dragOver(com.vaadin.terminal.gwt.client.ui.dd.VDragEvent) */ @Override public void dragOver(VDragEvent drag) { updateDragDetails(drag); postOverHook(drag); // Validate the drop validate(new VAcceptCallback() { public void accepted(VDragEvent event) { moveDragImageInLayout(event); } }, drag); }; }; } dropHandler.updateAcceptRules(childUidl); } /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.VCssLayout#updateFromUIDL(com.vaadin * .terminal.gwt.client.UIDL, * com.vaadin.terminal.gwt.client.ApplicationConnection) */ @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { if (client.updateComponent(this, uidl, true)) { return; } this.client = client; // Drag mode handleDragModeUpdate(uidl); // Drop handlers UIDL c = null; for (final Iterator<Object> it = uidl.getChildIterator(); it.hasNext();) { c = (UIDL) it.next(); if (c.getTag().equals("-ac")) { updateDropHandler(c); break; } } UIDL modifiedUIDL = VDragDropUtil.removeDragDropCriteraFromUIDL(uidl); super.updateFromUIDL(modifiedUIDL, client); // Handle drop ratio settings handleCellDropRatioUpdate(modifiedUIDL); /* * Always check for iframe covers so new added/removed components get * covered */ iframeCoverUtility.setIframeCoversEnabled(iframeCoversEnabled, getElement()); // Drag filters dragFilter.update(modifiedUIDL, client); } /** * Handle updates to the dragmode * * @param uidl * The recieved UIDL */ private void handleDragModeUpdate(UIDL uidl) { if (uidl.hasAttribute(VHasDragMode.DRAGMODE_ATTRIBUTE)) { LayoutDragMode[] modes = LayoutDragMode.values(); dragMode = modes[uidl .getIntAttribute(VHasDragMode.DRAGMODE_ATTRIBUTE)]; ddHandler.updateDragMode(dragMode); if (dragMode != LayoutDragMode.NONE) { // Cover iframes if necessery iframeCoversEnabled = uidl .getBooleanAttribute(IframeCoverUtility.SHIM_ATTRIBUTE); // Listen to mouse down events ddHandler.attach(); } else if (dragMode == LayoutDragMode.NONE) { // Remove iframe covers iframeCoversEnabled = false; // Remove mouse down handler ddHandler.detach(); } } } /* * (non-Javadoc) * * @see com.google.gwt.user.client.ui.Widget#onUnload() */ @Override protected void onUnload() { super.onUnload(); ddHandler.detach(); iframeCoverUtility.setIframeCoversEnabled(false, getElement()); } /** * A hook for extended components to post process the the drop before it is * sent to the server. Useful if you don't want to override the whole drop * handler. */ protected boolean postDropHook(VDragEvent drag) { // Extended classes can add content here... return true; } /** * A hook for extended components to post process the the enter event. * Useful if you don't want to override the whole drophandler. */ protected void postEnterHook(VDragEvent drag) { // Extended classes can add content here... } /** * A hook for extended components to post process the the leave event. * Useful if you don't want to override the whole drophandler. */ protected void postLeaveHook(VDragEvent drag) { // Extended classes can add content here... } /** * A hook for extended components to post process the the over event. Useful * if you don't want to override the whole drophandler. */ protected void postOverHook(VDragEvent drag) { // Extended classes can add content here... } private Element placeHolderElement; private void attachDragImageToLayout(VDragEvent drag) { if (placeHolderElement == null) { placeHolderElement = DOM.createDiv(); placeHolderElement.setInnerHTML(" "); } } private void updatePlaceHolderStyleProperties(VDragEvent drag) { Widget dragged = (Widget) drag.getTransferable().getData( Constants.TRANSFERABLE_DETAIL_COMPONENT); if (dragged != null) { int height = Util.getRequiredHeight(dragged); int width = Util.getRequiredWidth(dragged); String className = dragged.getElement().getClassName(); className = className.replaceAll( VLayoutDragDropMouseHandler.ACTIVE_DRAG_SOURCE_STYLENAME, ""); placeHolderElement.setClassName(className + " " + DRAG_SHADOW_STYLE_NAME); placeHolderElement.getStyle().setWidth(width, Unit.PX); placeHolderElement.getStyle().setHeight(height, Unit.PX); } } private void detachDragImageFromLayout(VDragEvent drag) { if (placeHolderElement != null) { if (placeHolderElement.hasParentElement()) { placeHolderElement.removeFromParent(); } placeHolderElement = null; } } /** * Updates the drop details while dragging. This is needed to ensure client * side criterias can validate the drop location. * * @param event * The drag event */ protected void updateDragDetails(VDragEvent event) { com.google.gwt.user.client.Element over = event.getElementOver(); if (placeHolderElement.isOrHasChild(over)) { // Dragging over the placeholder return; } Widget widget = (Widget) Util.findWidget(over, null); if (widget == null) { // Null check return; } int offset = 0; int index = -1; for (int i = 0; i < getWidget().getElement().getChildCount(); i++) { Element child = getWidget().getElement().getChild(i).cast(); if (child.isOrHasChild(placeHolderElement)) { offset--; } else if (child.isOrHasChild(widget.getElement())) { index = i + offset; break; } } event.getDropDetails().put(Constants.DROP_DETAIL_TO, index); /* * The horizontal position within the cell */ event.getDropDetails().put( Constants.DROP_DETAIL_HORIZONTAL_DROP_LOCATION, getHorizontalDropLocation(widget, event)); /* * The vertical position within the cell */ event.getDropDetails().put( Constants.DROP_DETAIL_VERTICAL_DROP_LOCATION, getVerticalDropLocation(widget, event)); // Add mouse event details MouseEventDetails details = new MouseEventDetails( event.getCurrentGwtEvent(), VDDCssLayout.this.getElement()); event.getDropDetails().put(Constants.DROP_DETAIL_MOUSE_EVENT, details.serialize()); } private void moveDragImageInLayout(VDragEvent drag) { if (placeHolderElement == null) { /* * Drag image might not have been detach due to lazy attaching in * the DragAndDropManager. Detach it again here if it has not been * detached. */ attachDragImageToLayout(drag); return; } if (drag.getElementOver().isOrHasChild(placeHolderElement)) { return; } if (placeHolderElement.hasParentElement()) { /* * Remove the placeholder from the DOM so we can reposition */ placeHolderElement.removeFromParent(); } Widget w = Util.findWidget(drag.getElementOver(), null); Widget dragged = (Widget) drag.getTransferable().getData( Constants.TRANSFERABLE_DETAIL_COMPONENT); if (w == dragged) { /* * Dragging drag image over the placeholder should not have any * effect (except placeholder should be removed) */ return; } if (w != null && w != this) { HorizontalDropLocation hl = getHorizontalDropLocation(w, drag); VerticalDropLocation vl = getVerticalDropLocation(w, drag); if (hl == HorizontalDropLocation.LEFT || vl == VerticalDropLocation.TOP) { Element prev = w.getElement().getPreviousSibling().cast(); if (prev == null || !dragged.getElement().isOrHasChild(prev)) { w.getElement().getParentElement() .insertBefore(placeHolderElement, w.getElement()); } } else if (hl == HorizontalDropLocation.RIGHT || vl == VerticalDropLocation.BOTTOM) { Element next = w.getElement().getNextSibling().cast(); if (next == null || !dragged.getElement().isOrHasChild(next)) { w.getElement().getParentElement() .insertAfter(placeHolderElement, w.getElement()); } } else { Element prev = w.getElement().getPreviousSibling().cast(); if (prev == null || !dragged.getElement().isOrHasChild(prev)) { w.getElement().getParentElement() .insertBefore(placeHolderElement, w.getElement()); } } } else { /* * First child or hoovering outside of current components */ getWidget().getElement().appendChild(placeHolderElement); } updatePlaceHolderStyleProperties(drag); } /** * Returns the horizontal location within the cell when hoovering over the * cell. By default the cell is devided into three parts: left,center,right * with the ratios 10%,80%,10%; * * @param container * The widget container * @param event * The drag event * @return The horizontal drop location */ private HorizontalDropLocation getHorizontalDropLocation(Widget container, VDragEvent event) { return VDragDropUtil.getHorizontalDropLocation(container.getElement(), Util.getTouchOrMouseClientX(event.getCurrentGwtEvent()), horizontalDropRatio); } /** * Returns the horizontal location within the cell when hoovering over the * cell. By default the cell is devided into three parts: left,center,right * with the ratios 10%,80%,10%; * * @param container * The widget container * @param event * The drag event * @return The horizontal drop location */ private VerticalDropLocation getVerticalDropLocation(Widget container, VDragEvent event) { return VDragDropUtil.getVerticalDropLocation(container.getElement(), Util.getTouchOrMouseClientY(event.getCurrentGwtEvent()), verticalDropRatio); } /** * Handles updates the the hoover zones of the cell which specifies at which * position a component is dropped over a cell * * @param uidl * The UIDL */ private void handleCellDropRatioUpdate(UIDL uidl) { if (uidl.hasAttribute(Constants.ATTRIBUTE_HORIZONTAL_DROP_RATIO)) { horizontalDropRatio = uidl .getFloatAttribute(Constants.ATTRIBUTE_HORIZONTAL_DROP_RATIO); } if (uidl.hasAttribute(Constants.ATTRIBUTE_VERTICAL_DROP_RATIO)) { verticalDropRatio = uidl .getFloatAttribute(Constants.ATTRIBUTE_VERTICAL_DROP_RATIO); } } }