/******************************************************************************* * Copyright (c) 2014, 2015 EclipseSource and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * EclipseSource - initial API and implementation ******************************************************************************/ package org.eclipse.swt.internal.dnd.droptargetkit; import static org.eclipse.rap.rwt.internal.protocol.ClientMessageConst.EVENT_PARAM_ITEM; import static org.eclipse.rap.rwt.internal.protocol.ClientMessageConst.EVENT_PARAM_TIME; import static org.eclipse.rap.rwt.internal.protocol.ClientMessageConst.EVENT_PARAM_X; import static org.eclipse.rap.rwt.internal.protocol.ClientMessageConst.EVENT_PARAM_Y; import static org.eclipse.rap.rwt.internal.lifecycle.WidgetUtil.find; import static org.eclipse.swt.internal.dnd.DNDUtil.getDataTypeChangedValue; import static org.eclipse.swt.internal.dnd.DNDUtil.getDetailChangedValue; import static org.eclipse.swt.internal.dnd.DNDUtil.getFeedbackChangedValue; import static org.eclipse.swt.internal.dnd.DNDUtil.hasDataTypeChanged; import static org.eclipse.swt.internal.dnd.DNDUtil.hasDetailChanged; import static org.eclipse.swt.internal.dnd.DNDUtil.hasFeedbackChanged; import static org.eclipse.swt.internal.dnd.DNDUtil.setDataTypeChanged; import static org.eclipse.swt.internal.dnd.DNDUtil.setDetailChanged; import static org.eclipse.swt.internal.dnd.DNDUtil.setFeedbackChanged; import java.util.ArrayList; import java.util.List; import org.eclipse.rap.json.JsonObject; import org.eclipse.rap.json.JsonValue; import org.eclipse.rap.rwt.client.ClientFile; import org.eclipse.rap.rwt.dnd.ClientFileTransfer; import org.eclipse.rap.rwt.internal.client.ClientFileImpl; import org.eclipse.rap.rwt.internal.lifecycle.LifeCycleUtil; import org.eclipse.rap.rwt.internal.lifecycle.ProcessActionRunner; import org.eclipse.rap.rwt.internal.protocol.WidgetOperationHandler; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.dnd.TransferData; import org.eclipse.swt.internal.dnd.DNDEvent; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Item; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; public class DropTargetOperationHandler extends WidgetOperationHandler<DropTarget> { private static final String EVENT_DRAG_ENTER = "DragEnter"; private static final String EVENT_DRAG_OPERATION_CHANGED = "DragOperationChanged"; private static final String EVENT_DRAG_OVER = "DragOver"; private static final String EVENT_DRAG_LEAVE = "DragLeave"; private static final String EVENT_DROP_ACCEPT = "DropAccept"; private static final String EVENT_PARAM_OPERATION = "operation"; private static final String EVENT_PARAM_FEEDBACK = "feedback"; private static final String EVENT_PARAM_SOURCE = "source"; private static final String EVENT_PARAM_DATATYPE = "dataType"; private static final String EVENT_PARAM_FILES = "files"; public DropTargetOperationHandler( DropTarget dropTarget ) { super( dropTarget ); } @Override public void handleNotify( DropTarget dropTarget, String eventName, JsonObject properties ) { if( EVENT_DRAG_ENTER.equals( eventName ) ) { handleNotifyDragEnter( dropTarget, properties ); } else if( EVENT_DRAG_OPERATION_CHANGED.equals( eventName ) ) { handleNotifyDragOperationChanged( dropTarget, properties ); } else if( EVENT_DRAG_OVER.equals( eventName ) ) { handleNotifyDragOver( dropTarget, properties ); } else if( EVENT_DRAG_LEAVE.equals( eventName ) ) { handleNotifyDragLeave( dropTarget, properties ); } else if( EVENT_DROP_ACCEPT.equals( eventName ) ) { handleNotifyDropAccept( dropTarget, properties ); } else { super.handleNotify( dropTarget, eventName, properties ); } } /* * PROTOCOL NOTIFY DragEnter * * @param x (int) the x coordinate of the pointer * @param y (int) the y coordinate of the pointer * @param time (int) the time when the event occurred * @param operation (string) the DND operation, could be "copy", "move" or "link" * @param feedback (int) bitwise OR of DND feedback flags as defined in DND class * @param item (string) the id of the item that the event occurred in * @param source (string) the id of the control, which is currently dragged */ public void handleNotifyDragEnter( final DropTarget dropTarget, final JsonObject properties ) { ProcessActionRunner.add( new Runnable() { @Override public void run() { int x = properties.get( EVENT_PARAM_X ).asInt(); int y = properties.get( EVENT_PARAM_Y ).asInt(); int time = properties.get( EVENT_PARAM_TIME ).asInt(); int detail = translateOperation( properties.get( EVENT_PARAM_OPERATION ).asString() ); int feedback = properties.get( EVENT_PARAM_FEEDBACK ).asInt(); Item item = getWidget( properties.get( EVENT_PARAM_ITEM ) ); Control sourceControl = getWidget( properties.get( EVENT_PARAM_SOURCE ) ); DragSource dragSource = getDragSource( sourceControl ); int operations = getOperations( dragSource, dropTarget ); TransferData[] dataTypes = determineDataTypes( dragSource, dropTarget ); DNDEvent event = createDropTargetEvent( x, y, time, detail, feedback, operations, dataTypes, dataTypes[ 0 ], item ); dropTarget.notifyListeners( DND.DragEnter, event ); if( event.detail != detail ) { changeOperation( dragSource, dropTarget, event.detail ); } // no check, dataType is always changed from null to a valid value: changeDataType( dragSource, dropTarget, event.dataType ); if( event.feedback != feedback ) { setFeedbackChanged( dropTarget.getControl(), event.feedback ); } } } ); } /* * PROTOCOL NOTIFY DragOperationChanged * * @param x (int) the x coordinate of the pointer * @param y (int) the y coordinate of the pointer * @param time (int) the time when the event occurred * @param operation (string) the DND operation, could be "copy", "move" or "link" * @param feedback (int) bitwise OR of DND feedback flags as defined in DND class * @param dataType (int) transfered data type * @param item (string) the id of the item that the event occurred in * @param source (string) the id of the control, which is currently dragged */ public void handleNotifyDragOperationChanged( final DropTarget dropTarget, final JsonObject properties ) { ProcessActionRunner.add( new Runnable() { @Override public void run() { int x = properties.get( EVENT_PARAM_X ).asInt(); int y = properties.get( EVENT_PARAM_Y ).asInt(); int time = properties.get( EVENT_PARAM_TIME ).asInt(); int detail = getOperation( properties.get( EVENT_PARAM_OPERATION ) ); int feedback = getFeedback( properties.get( EVENT_PARAM_FEEDBACK ) ); TransferData dataType = getDataType( properties.get( EVENT_PARAM_DATATYPE ) ); Item item = getWidget( properties.get( EVENT_PARAM_ITEM ) ); Control sourceControl = getWidget( properties.get( EVENT_PARAM_SOURCE ) ); DragSource dragSource = getDragSource( sourceControl ); int operations = getOperations( dragSource, dropTarget ); TransferData[] dataTypes = determineDataTypes( dragSource, dropTarget ); DNDEvent event = createDropTargetEvent( x, y, time, detail, feedback, operations, dataTypes, dataType, item ); dropTarget.notifyListeners( DND.DragOperationChanged, event ); if( event.detail != detail ) { changeOperation( dragSource, dropTarget, event.detail ); } if( event.dataType != dataType ) { changeDataType( dragSource, dropTarget, event.dataType ); } if( event.feedback != feedback ) { setFeedbackChanged( dropTarget.getControl(), event.feedback ); } } } ); } /* * PROTOCOL NOTIFY DragOver * * @param x (int) the x coordinate of the pointer * @param y (int) the y coordinate of the pointer * @param time (int) the time when the event occurred * @param operation (string) the DND operation, could be "copy", "move" or "link" * @param feedback (int) bitwise OR of DND feedback flags as defined in DND class * @param dataType (int) transfered data type * @param item (string) the id of the item that the event occurred in * @param source (string) the id of the control, which is currently dragged */ public void handleNotifyDragOver( final DropTarget dropTarget, final JsonObject properties ) { ProcessActionRunner.add( new Runnable() { @Override public void run() { int x = properties.get( EVENT_PARAM_X ).asInt(); int y = properties.get( EVENT_PARAM_Y ).asInt(); int time = properties.get( EVENT_PARAM_TIME ).asInt(); int detail = getOperation( properties.get( EVENT_PARAM_OPERATION ) ); int feedback = getFeedback( properties.get( EVENT_PARAM_FEEDBACK ) ); TransferData dataType = getDataType( properties.get( EVENT_PARAM_DATATYPE ) ); Item item = getWidget( properties.get( EVENT_PARAM_ITEM ) ); Control sourceControl = getWidget( properties.get( EVENT_PARAM_SOURCE ) ); DragSource dragSource = getDragSource( sourceControl ); int operations = getOperations( dragSource, dropTarget ); TransferData[] dataTypes = determineDataTypes( dragSource, dropTarget ); DNDEvent event = createDropTargetEvent( x, y, time, detail, feedback, operations, dataTypes, dataType, item ); dropTarget.notifyListeners( DND.DragOver, event ); if( event.detail != detail ) { changeOperation( dragSource, dropTarget, event.detail ); } if( event.dataType != dataType ) { changeDataType( dragSource, dropTarget, event.dataType ); } if( event.feedback != feedback ) { setFeedbackChanged( dropTarget.getControl(), event.feedback ); } } } ); } /* * PROTOCOL NOTIFY DragLeave * * @param x (int) the x coordinate of the pointer * @param y (int) the y coordinate of the pointer * @param time (int) the time when the event occurred * @param operation (string) the DND operation, could be "copy", "move" or "link" */ public void handleNotifyDragLeave( final DropTarget dropTarget, final JsonObject properties ) { ProcessActionRunner.add( new Runnable() { @Override public void run() { int x = properties.get( EVENT_PARAM_X ).asInt(); int y = properties.get( EVENT_PARAM_Y ).asInt(); int time = properties.get( EVENT_PARAM_TIME ).asInt(); int detail = translateOperation( properties.get( EVENT_PARAM_OPERATION ).asString() ); DNDEvent event = createDropTargetEvent( x, y, time, detail, 0, 0, null, null, null ); dropTarget.notifyListeners( DND.DragLeave, event ); } } ); } /* * PROTOCOL NOTIFY DropAccept * * @param x (int) the x coordinate of the pointer * @param y (int) the y coordinate of the pointer * @param time (int) the time when the event occurred * @param operation (string) the DND operation, could be "copy", "move" or "link" * @param dataType (int) transfered data type * @param item (string) the id of the item that the event occurred in * @param source (string) the id of the control, which is currently dragged */ public void handleNotifyDropAccept( final DropTarget dropTarget, final JsonObject properties ) { ProcessActionRunner.add( new Runnable() { @Override public void run() { DropData dropData = createDropData( dropTarget, properties ); if( !isFileDrop( dropData ) ) { // In this case no DRAG_ENTER was fired by the client fireDragLeave( dropTarget, dropData ); // DRAG_LEAVE was suppressed by the client } fireDropAccept( dropTarget, dropData ); if( dropData.detail != DND.DROP_NONE && dropData.dataType != null ) { if( !isFileDrop( dropData ) ) { fireSetData( dropTarget, dropData ); } fireDrop( dropTarget, dropData ); } } } ); } private static void fireDragLeave( DropTarget dropTarget, DropData dropData ) { DNDEvent leaveEvent = createDropTargetEvent( dropData.x, dropData.y, dropData.time, dropData.detail, 0, 0, null, null, null ); dropTarget.notifyListeners( DND.DragLeave, leaveEvent ); } private static void fireDropAccept( DropTarget dropTarget, DropData dropData ) { DNDEvent acceptEvent = createDropTargetEvent( dropData.x, dropData.y, dropData.time, dropData.detail, 0, dropData.operations, null, dropData.dataType, dropData.item ); dropTarget.notifyListeners( DND.DropAccept, acceptEvent ); dropData.detail = changeOperation( dropData.dragSource, dropTarget, acceptEvent.detail ); dropData.dataTypes = determineDataTypes( dropData.dragSource, dropTarget ); dropData.dataType = checkDataType( acceptEvent.dataType, dropData.dataTypes ); } private static void fireSetData( DropTarget dropTarget, DropData dropData ) { DNDEvent setDataEvent = createDragSetDataEvent( dropData.x, dropData.y, dropData.dataType ); dropData.dragSource.notifyListeners( DND.DragSetData, setDataEvent ); dropData.data = transferData( dropTarget, dropData.dataType, setDataEvent ); } private static void fireDrop( DropTarget dropTarget, DropData dropData ) { DNDEvent dropEvent = createDropEvent( dropData ); dropEvent.data = dropData.data; dropTarget.notifyListeners( DND.Drop, dropEvent ); changeOperation( dropData.dragSource, dropTarget, dropEvent.detail ); // needed for DRAG_END } private static boolean isFileDrop( DropData dropData ) { return dropData.data instanceof ClientFileImpl[]; } private static DNDEvent createDropEvent( DropData dropData ) { return createDropTargetEvent( dropData.x, dropData.y, 0, dropData.detail, 0, dropData.operations, dropData.dataTypes, dropData.dataType, dropData.item ); } private static DropData createDropData( DropTarget dropTarget, JsonObject properties ) { DropData dropData = new DropData(); dropData.data = getClientFiles( properties.get( EVENT_PARAM_FILES ) ); dropData.x = properties.get( EVENT_PARAM_X ).asInt(); dropData.y = properties.get( EVENT_PARAM_Y ).asInt(); dropData.time = properties.get( EVENT_PARAM_TIME ).asInt(); dropData.detail = getOperation( properties.get( EVENT_PARAM_OPERATION ) ); dropData.dataType = dropData.data != null ? getClientFileDataType() : getDataType( properties.get( EVENT_PARAM_DATATYPE ) ); dropData.dragSource = getDragSource( properties.get( EVENT_PARAM_SOURCE ) ); dropData.dataTypes = determineDataTypes( dropData.dragSource, dropTarget ); dropData.item = getWidget( properties.get( EVENT_PARAM_ITEM ) ); dropData.operations = getOperations( dropData.dragSource, dropTarget ); return dropData; } private static ClientFile[] getClientFiles( JsonValue value ) { if( value != null ) { JsonObject fileProperties = value.asObject(); List<String> fileIds = fileProperties.names(); int filecount = fileIds.size(); ClientFileImpl[] result = new ClientFileImpl[ filecount ]; for( int i = 0; i < filecount; i++ ) { JsonObject file = ( JsonObject )fileProperties.get( fileIds.get( i ) ); result[ i ] = new ClientFileImpl( fileIds.get( i ), file.get( "name" ).asString(), file.get( "type" ).asString(), file.get( "size" ).asLong() ); } return result; } return null; } private static DragSource getDragSource( JsonValue sourceId ) { Control sourceControl = getWidget( sourceId ); DragSource dragSource = sourceControl != null ? getDragSource( sourceControl ) : null; return dragSource; } private static DNDEvent createDropTargetEvent( int x, int y, int time, int detail, int feedback, int operations, TransferData[] dataTypes, TransferData dataType, Item item ) { DNDEvent event = new DNDEvent(); event.x = x; event.y = y; event.time = time; event.detail = detail; event.feedback = feedback; event.operations = operations; event.dataTypes = dataTypes; event.dataType = dataType; event.item = item; event.doit = true; return event; } private static DNDEvent createDragSetDataEvent( int x, int y, TransferData dataType ) { DNDEvent result = new DNDEvent(); result.detail = DND.DROP_NONE; result.dataType = dataType; result.x = x; result.y = y; result.data = null; result.doit = true; return result; } private static int getOperation( JsonValue operation ) { int result; if( hasDetailChanged() ) { result = getDetailChangedValue(); } else { result = translateOperation( operation.asString() ); } return result; } private static int translateOperation( String operation ) { int result = DND.DROP_NONE; if( "copy".equals( operation ) ) { result = DND.DROP_COPY; } else if( "move".equals( operation ) ) { result = DND.DROP_MOVE; } else if( "link".equals( operation ) ) { result = DND.DROP_LINK; } return result; } private static int changeOperation( DragSource dragSource, DropTarget dropTarget, int detail ) { int checkedOperation = checkOperation( dragSource, dropTarget, detail ); setDetailChanged( dropTarget.getControl(), checkedOperation ); return checkedOperation; } private static int checkOperation( DragSource dragSource, DropTarget dropTarget, int operation ) { int allowedOperations = getOperations( dragSource, dropTarget ); return ( allowedOperations & operation ) != 0 ? operation : DND.DROP_NONE; } private static int getOperations( DragSource dragSource, DropTarget dropTarget ) { if( dragSource == null ) { return dropTarget.getStyle(); } return( dragSource.getStyle() & dropTarget.getStyle() ); } private static TransferData getDataType( JsonValue dataType ) { TransferData result = null; if( hasDataTypeChanged() ) { result = getDataTypeChangedValue(); } else if( dataType != null && !dataType.isNull() ) { result = new TransferData(); result.type = dataType.asInt(); } return result; } private static TransferData getClientFileDataType() { TransferData result = new TransferData(); result.type = ClientFileTransfer.getInstance().getSupportedTypes()[ 0 ].type; return result; } private static void changeDataType( DragSource dragSource, DropTarget dropTarget, TransferData dataType ) { TransferData[] validDataTypes = determineDataTypes( dragSource, dropTarget ); TransferData value = checkDataType( dataType, validDataTypes ); // [tb] If the value is not valid, another valid value will be set. // This is simplified from SWT, where null would be set. if( value == null ) { value = validDataTypes[ 0 ]; } setDataTypeChanged( dropTarget.getControl(), value ); } static TransferData[] determineDataTypes( DragSource dragSource, DropTarget dropTarget ) { List<TransferData> supportedTypes = new ArrayList<>(); for( Transfer dropTargetTransfer : dropTarget.getTransfer() ) { TransferData[] dataTypes = dropTargetTransfer.getSupportedTypes(); for( TransferData dataType : dataTypes ) { if( dragSource == null || transfersSupport( dragSource.getTransfer(), dataType ) ) { supportedTypes.add( dataType ); } } } return supportedTypes.toArray( new TransferData[ 0 ] ); } private static boolean transfersSupport( Transfer[] transfers, TransferData dataType ) { for( Transfer dropTargetTransfer : transfers ) { if( dropTargetTransfer.isSupportedType( dataType ) ) { return true; } } return false; } private static TransferData checkDataType( TransferData dataType, TransferData[] validTypes ) { for( TransferData validType : validTypes ) { if( TransferData.sameType( dataType, validType ) ) { return dataType; } } return null; } private static int getFeedback( JsonValue feedback ) { int result; if( hasFeedbackChanged() ) { result = getFeedbackChangedValue(); } else { result = feedback.asInt(); } return result; } @SuppressWarnings( "unchecked" ) private static <T> T getWidget( JsonValue itemId ) { if( itemId != null && !itemId.isNull() ) { return ( T )findWidgetById( itemId.asString() ); } return null; } private static Widget findWidgetById( String id ) { for( Shell shell : LifeCycleUtil.getSessionDisplay().getShells() ) { Widget widget = find( shell, id ); if( widget != null ) { return widget; } } return null; } private static DragSource getDragSource( Control control ) { return ( DragSource )control.getData( DND.DRAG_SOURCE_KEY ); } private static Object transferData( DropTarget dropTarget, TransferData dataType, DNDEvent setDataEvent ) { if( setDataEvent.doit ) { Transfer transfer = findTransferByType( dataType, dropTarget ); transfer.javaToNative( setDataEvent.data, dataType ); return transfer.nativeToJava( dataType ); } return null; } private static Transfer findTransferByType( TransferData type, DropTarget dropTarget ) { for( Transfer supported : dropTarget.getTransfer() ) { if( supported.isSupportedType( type ) ) { return supported; } } return null; } private static class DropData { int x; int y; int operations; Item item; DragSource dragSource; TransferData dataType; TransferData[] dataTypes; int detail; int time; Object data; } }