/* * 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.user.client.DOM; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.UIObject; 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.VGridLayout; 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 com.vaadin.terminal.gwt.client.ui.layout.ChildComponentContainer; import fi.jasoft.dragdroplayouts.client.ui.VLayoutDragDropMouseHandler.DragStartListener; import fi.jasoft.dragdroplayouts.client.ui.interfaces.VHasDragMode; import fi.jasoft.dragdroplayouts.client.ui.util.IframeCoverUtility; public class VDDGridLayout extends VGridLayout implements VHasDragMode, VHasDropHandler, DragStartListener { public static final String CLASSNAME = "v-ddgridlayout"; public static final String OVER = CLASSNAME + "-over"; public static final float DEFAULT_HORIZONTAL_RATIO = 0.2f; public static final float DEFAULT_VERTICAL_RATIO = 0.2f; private VAbstractDropHandler dropHandler; private LayoutDragMode dragMode = LayoutDragMode.NONE; private final HTML dragShadow = new HTML(""); private float cellLeftRightDropRatio = DEFAULT_HORIZONTAL_RATIO; private float cellTopBottomDropRatio = DEFAULT_VERTICAL_RATIO; protected final AbsolutePanel canvas; protected ApplicationConnection client; protected boolean iframeCoversEnabled = false; private final VDragFilter dragFilter = new VDragFilter(); private final IframeCoverUtility iframeCoverUtility = new IframeCoverUtility(); public VDDGridLayout() { super(); canvas = (AbsolutePanel) getWidget(); ddMouseHandler.addDragStartListener(this); } /* * (non-Javadoc) * * @see com.google.gwt.user.client.ui.Widget#onUnload() */ @Override protected void onUnload() { super.onUnload(); ddMouseHandler.detach(); iframeCoverUtility.setIframeCoversEnabled(false, getElement()); } // The drag mouse handler which handles the creation of the transferable private final VLayoutDragDropMouseHandler ddMouseHandler = new VLayoutDragDropMouseHandler( this, dragMode); /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.VGridLayout#updateFromUIDL(com.vaadin * .terminal.gwt.client.UIDL, * com.vaadin.terminal.gwt.client.ApplicationConnection) */ @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { super.updateFromUIDL(uidl, client); this.client = client; // Update drop handler if necessary UIDL c = null; for (final Iterator<Object> it = uidl.getChildIterator(); it.hasNext();) { c = (UIDL) it.next(); if (c.getTag().equals("-ac")) { updateDropHandler(c); break; } } handleDragModeUpdate(uidl); handleCellDropRatioUpdate(uidl); // Iframe cover check iframeCoverUtility.setIframeCoversEnabled(iframeCoversEnabled, getElement()); dragFilter.update(uidl, client); } /** * Returns the drop handler used when the user drops a component over the * Grid Layout */ public VDropHandler getDropHandler() { return dropHandler; } /** * Handles drag mode changes recieved from the server * * @param uidl * The UIDL */ private void handleDragModeUpdate(UIDL uidl) { if (uidl.hasAttribute(VHasDragMode.DRAGMODE_ATTRIBUTE)) { LayoutDragMode[] modes = LayoutDragMode.values(); dragMode = modes[uidl .getIntAttribute(VHasDragMode.DRAGMODE_ATTRIBUTE)]; ddMouseHandler.updateDragMode(dragMode); if (dragMode != LayoutDragMode.NONE) { // Cover iframes if necessery iframeCoversEnabled = true; // Listen to mouse down events ddMouseHandler.attach(); } else if (dragMode == LayoutDragMode.NONE) { // Remove iframe covers iframeCoversEnabled = false; // Remove mouse down handler ddMouseHandler.detach(); } } } private void updateDropDetails(VDragEvent event) { CellDetails cd = getCellDetails(event); if (cd != null) { // Add row event.getDropDetails().put(Constants.DROP_DETAIL_ROW, Integer.valueOf(cd.row)); // Add column event.getDropDetails().put(Constants.DROP_DETAIL_COLUMN, Integer.valueOf(cd.column)); // Add horizontal position HorizontalDropLocation hl = getHorizontalDropLocation(cd, event); event.getDropDetails().put( Constants.DROP_DETAIL_HORIZONTAL_DROP_LOCATION, hl); // Add vertical position VerticalDropLocation vl = getVerticalDropLocation(cd, event); event.getDropDetails().put( Constants.DROP_DETAIL_VERTICAL_DROP_LOCATION, vl); // Check if the cell we are hovering over has content boolean hasContent = false; ChildComponentContainer container = null; for (ChildComponentContainer cont : widgetToComponentContainer .values()) { if (DOM.isOrHasChild(cont.getElement(), event.getElementOver())) { hasContent = true; container = cont; break; } } event.getDropDetails().put(Constants.DROP_DETAIL_EMPTY_CELL, !hasContent); if (hasContent) { /* * Add Classname of component over the drag. This can be used by * a a client side criteria to verify that a drag is over a * specific class of component. */ Widget w = container.getWidget(); if (w != null) { String className = w.getClass().getName(); event.getDropDetails().put( Constants.DROP_DETAIL_OVER_CLASS, className); } else { event.getDropDetails().put( Constants.DROP_DETAIL_OVER_CLASS, VDDGridLayout.this); } } // Add mouse event details MouseEventDetails details = new MouseEventDetails( event.getCurrentGwtEvent(), getElement()); event.getDropDetails().put(Constants.DROP_DETAIL_MOUSE_EVENT, details.serialize()); } } /** * Returns the horizontal drop location * * @param cell * The cell details * @param event * The drag event * @return */ private HorizontalDropLocation getHorizontalDropLocation(CellDetails cell, VDragEvent event) { // Get the horizontal location HorizontalDropLocation hdetail; int x = Util.getTouchOrMouseClientX(event.getCurrentGwtEvent()) - canvas.getAbsoluteLeft() - cell.x; assert (x >= 0 && x <= cell.width); if (x < cell.width * cellLeftRightDropRatio) { hdetail = HorizontalDropLocation.LEFT; } else if (x < cell.width * (1.0 - cellLeftRightDropRatio)) { hdetail = HorizontalDropLocation.CENTER; } else { hdetail = HorizontalDropLocation.RIGHT; } return hdetail; } /** * Returns the vertical drop location * * @param cell * The cell details * @param event * The drag event * @return */ private VerticalDropLocation getVerticalDropLocation(CellDetails cell, VDragEvent event) { // Get the vertical location VerticalDropLocation vdetail; int y = Util.getTouchOrMouseClientY(event.getCurrentGwtEvent()) - canvas.getAbsoluteTop() - cell.y; assert (y >= 0 && y <= cell.height); if (y < cell.height * cellTopBottomDropRatio) { vdetail = VerticalDropLocation.TOP; } else if (y < cell.height * (1.0 - cellTopBottomDropRatio)) { vdetail = VerticalDropLocation.MIDDLE; } else { vdetail = VerticalDropLocation.BOTTOM; } return vdetail; } /** * Emphasizes a component container when user is hovering a dragged * component over the container. * * @param container * The container * @param event */ protected void emphasis(CellDetails cell, VDragEvent event) { // Remove any existing empasis deEmphasis(); // Ensure we are not dragging ourself into ourself Widget draggedComponent = (Widget) event.getTransferable().getData( Constants.TRANSFERABLE_DETAIL_COMPONENT); if (draggedComponent == VDDGridLayout.this) { return; } HorizontalDropLocation hl = getHorizontalDropLocation(cell, event); VerticalDropLocation vl = getVerticalDropLocation(cell, event); // Apply over style UIObject.setStyleName(dragShadow.getElement(), OVER, true); // Add vertical location dependent style UIObject.setStyleName(dragShadow.getElement(), OVER + "-" + vl.toString().toLowerCase(), true); // Add horizontal location dependent style UIObject.setStyleName(dragShadow.getElement(), OVER + "-" + hl.toString().toLowerCase(), true); } /** * Removes any emphasis previously set by emphasis */ private void deEmphasis() { UIObject.setStyleName(dragShadow.getElement(), OVER, false); // Horizontal styles UIObject.setStyleName(dragShadow.getElement(), OVER + "-" + HorizontalDropLocation.LEFT.toString().toLowerCase(), false); UIObject.setStyleName(dragShadow.getElement(), OVER + "-" + HorizontalDropLocation.CENTER.toString().toLowerCase(), false); UIObject.setStyleName(dragShadow.getElement(), OVER + "-" + HorizontalDropLocation.RIGHT.toString().toLowerCase(), false); // Vertical styles UIObject.setStyleName(dragShadow.getElement(), OVER + "-" + VerticalDropLocation.TOP.toString().toLowerCase(), false); UIObject.setStyleName(dragShadow.getElement(), OVER + "-" + VerticalDropLocation.MIDDLE.toString().toLowerCase(), false); UIObject.setStyleName(dragShadow.getElement(), OVER + "-" + VerticalDropLocation.BOTTOM.toString().toLowerCase(), false); } public LayoutDragMode getDragMode() { return dragMode; } /** * 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)) { cellLeftRightDropRatio = uidl .getFloatAttribute(Constants.ATTRIBUTE_HORIZONTAL_DROP_RATIO); } if (uidl.hasAttribute(Constants.ATTRIBUTE_VERTICAL_DROP_RATIO)) { cellTopBottomDropRatio = uidl .getFloatAttribute(Constants.ATTRIBUTE_VERTICAL_DROP_RATIO); } } /** * 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... } /** * 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); } /** * Creates a drop handler if one has not alread been created and updates it * with the details recieved from the server. * * @param childUidl * The UIDL */ protected void updateDropHandler(UIDL childUidl) { if (dropHandler == null) { dropHandler = new VAbstractDropHandler() { public ApplicationConnection getApplicationConnection() { return client; }; /* * (non-Javadoc) * * @see * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler * #getPaintable() */ @Override public Paintable getPaintable() { return VDDGridLayout.this; } /* * (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) { // Nop } /* * (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) { // Add the marker that shows the drop location while // dragging canvas.insert(dragShadow, 0); postEnterHook(drag); }; /* * (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) { // Update the detail of the drop updateDropDetails(drag); // Remove emphasis deEmphasis(); // Remove the drag shadow canvas.remove(dragShadow); return postDropHook(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) { // Remove emphasis from previous selection deEmphasis(); // Update the drop details so we can then validate them updateDropDetails(drag); postOverHook(drag); // Emphasis drop location validate(new VAcceptCallback() { public void accepted(VDragEvent event) { CellDetails cd = getCellDetails(event); if (cd != null) { dragShadow.setWidth(cd.width + "px"); dragShadow.setHeight(cd.height + "px"); canvas.setWidgetPosition(dragShadow, cd.x, cd.y); emphasis(cd, event); } } }, 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) { deEmphasis(); canvas.remove(dragShadow); postLeaveHook(drag); super.dragLeave(drag); }; }; } // Update the rules dropHandler.updateAcceptRules(childUidl); } /** * A helper class returned by getCellDetailsByCoordinates() which contains * positional and size data of the cell. */ protected class CellDetails { public int row = -1; public int column = -1; public int x = -1; public int y = -1; public int width = -1; public int height = -1; } private CellDetails getCellDetails(VDragEvent event) { int x = Util.getTouchOrMouseClientX(event.getCurrentGwtEvent()) - canvas.getAbsoluteLeft(); int y = Util.getTouchOrMouseClientY(event.getCurrentGwtEvent()) - canvas.getAbsoluteTop(); return getCellDetailsByCoordinates(x, y); } /** * Returns details of the cell under the given position * * @param x * The x-coordinate * @param y * The y-coordinate * @return The details of the cell under the coordinate */ private CellDetails getCellDetailsByCoordinates(int x, int y) { CellDetails cd = new CellDetails(); int[] columnWidths = getColumnWidths(); int[] rowHeights = getRowHeights(); // Get column and x coordinate int temp = 0; for (int col = 0; col < columnWidths.length; col++) { if (x >= temp && x <= temp + columnWidths[col]) { cd.column = col; cd.x = temp; cd.width = columnWidths[col]; break; } temp += columnWidths[col] + getHorizontalSpacing(); } // get row temp = 0; for (int row = 0; row < rowHeights.length; row++) { if (y >= temp && y <= temp + rowHeights[row]) { cd.row = row; cd.y = temp; cd.height = rowHeights[row]; break; } temp += rowHeights[row] + getVerticalSpacing(); } // Sanity check if (cd.row == -1 || cd.column == -1 || cd.width == -1 || cd.height == -1) { return null; } return cd; } }