/*******************************************************************************
* Copyright (c) 2012 Arapiki Solutions Inc.
* 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:
* psmith - initial API and
* implementation and/or initial documentation
*******************************************************************************/
package com.buildml.eclipse.outline;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerDropAdapter;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import com.buildml.eclipse.MainEditor;
import com.buildml.eclipse.bobj.UIInteger;
import com.buildml.eclipse.utils.UndoOpAdapter;
import com.buildml.eclipse.utils.dnd.BuildMLTransfer;
import com.buildml.eclipse.utils.dnd.BuildMLTransferType;
import com.buildml.model.IBuildStore;
import com.buildml.model.IPackageMgr;
import com.buildml.model.undo.PackageUndoOp;
import com.buildml.utils.errors.ErrorCode;
/**
* Functionality related to dropping items into the outline content view, possibly onto
* itself (i.e. rearranging nodes in the tree).
*
* @author Peter Smith <psmith@arapiki.com>
*/
public class OutlineDropTarget extends ViewerDropAdapter {
/*=====================================================================================*
* FIELDS/TYPES
*=====================================================================================*/
/** The TreeViewer we're dropping an element onto */
private TreeViewer treeViewer;
/** The BuildStore underlying the main editor */
private IBuildStore buildStore;
/** The main BuildML editor we're operating on */
private MainEditor mainEditor;
/** The PackageMgr associated with the BuildStore */
private IPackageMgr pkgMgr;
/*=====================================================================================*
* CONSTRUCTORS
*=====================================================================================*/
/**
* Create a new OutlineDropTarget object. There should be exactly one of these objects
* for each OutlineContentPage object (view).
*
* @param treeViewer The TreeViewer that elements will be dragged from.
* @param outlinePage The outline content view.
*/
public OutlineDropTarget(TreeViewer treeViewer, OutlinePage outlinePage) {
super(treeViewer);
this.treeViewer = treeViewer;
this.mainEditor = outlinePage.getMainEditor();
this.buildStore = mainEditor.getBuildStore();
this.pkgMgr = this.buildStore.getPackageMgr();
/* register ourselves with the drag/drop framework - we can receive drops */
treeViewer.addDropSupport(DND.DROP_MOVE | DND.DROP_COPY,
new Transfer[] { BuildMLTransfer.getInstance() },
this);
}
/*=====================================================================================*
* PUBLIC METHODS
*=====================================================================================*/
/**
* Provide feedback to the drag/drop system on whether the currently hovered-over item
* is a valid drop target. In our case, all tree nodes are valid drop targets (even though
* we may actually drop into the node's parent folder).
*/
@Override
public boolean validateDrop(Object target, int operation, TransferData transferType) {
/*
* From a visual feedback perspective, an item can be dropped if it's a BuildMLTransfer
* type (file, action, package, etc). Later, in the performDrop() method, we can look
* at the actual source/target data to make sure the drop is legal.
*/
return BuildMLTransfer.getInstance().isSupportedType(transferType);
}
/*-------------------------------------------------------------------------------------*/
/**
* A drag/drop operation has completed, and we must now update the model and the view
* appropriately. At this point, we also do extra validation to make sure we're allowed
* to drop the source onto the target. The source is either a UIAction, a UIPackage
* or UIPackageFolder, and the destination is a UIPackage or UIPackageFolder.
*/
@Override
public boolean performDrop(Object data) {
/* do we need to refresh the outline view as a result of the drop? */
boolean outlineRefreshNeeded = false;
/* is there anything about the editor that is now dirty? (to be saved) */
boolean editorDirty = false;
/* the target must be a package or folder, regardless of the source. */
UIInteger target = (UIInteger)getCurrentTarget();
if (target == null) {
return false;
}
/*
* We only support drops of BuildMLTransferType arrays. We'll treat each
* element of the array as a separate drop, and if one drop fails, we
* silently continue with other drops.
*/
if (data instanceof BuildMLTransferType[]) {
BuildMLTransferType myTypes[] = (BuildMLTransferType[])data;
for (int index = 0; index != myTypes.length; index++) {
/* Require that the drop is from the same BuildStore as the target */
if (buildStore.toString().equals(myTypes[index].owner)){
/*
* If dropping a UIPackage or UIPackageFolder, we restructure
* that package's hierarchy within the outline view.
*/
if ((myTypes[index].type == BuildMLTransferType.TYPE_PACKAGE) ||
(myTypes[index].type == BuildMLTransferType.TYPE_PACKAGE_FOLDER)) {
/* perform the drop - on failure, skip to next element */
performDropUIPackage(myTypes[index], target);
}
}
}
}
/* transfer probably succeeded (although individual drops may have failed) */
return true;
}
/*-------------------------------------------------------------------------------------*/
/**
* Perform the drop of a UIPackage or UIPackageFolder onto a UIPackage or UIPackageFolder.
* This restructures the hierarchy of packages as they appear in the Outline view. Dropping
* onto a UIPackage will reparent at the same location in the tree as the target UIPackage.
*
* @param droppedObj BuildStore packageId of the UIPackage/UIPackageFolder being dropped.
* @param targetObj The BuildStore packageId of the UIPackage/UIPackageFolder being dropped into.
* @return True on success, or false if the drop failed for any reason.
*/
private boolean performDropUIPackage(BuildMLTransferType droppedObj, UIInteger targetObj) {
/*
* Determine the folder where the item will be dropped. If the target
* is a package (not a folder), we'll drop the item into the target's
* parent folder.
*/
int targetId = targetObj.getId();
if (!pkgMgr.isFolder(targetId)) {
targetId = pkgMgr.getParent(targetId);
if (targetId == ErrorCode.NOT_FOUND) {
return false;
}
}
/*
* If the dropped object is being dropped onto its current parent (instead of a
* new parent), we don't need to do anything.
*/
int nodeId = droppedObj.id;
int parentId = pkgMgr.getParent(nodeId);
if (parentId == targetId) {
return false;
}
/*
* Attempt to move the incoming item to a new parent. On error,
* we fail silently (aborting the drag).
*/
if (pkgMgr.setParent(nodeId, targetId) != ErrorCode.OK) {
return false;
}
/* set the parent folder's state to "expanded" to show the dropped element */
treeViewer.setExpandedState(new UIInteger(targetId), true);
/*
* Each drop of a package into a package folder is treated as an individual operation,
* even if multiple were dropped at the same time.
*/
PackageUndoOp op = new PackageUndoOp(buildStore, nodeId);
op.recordMove(parentId, targetId);
new UndoOpAdapter("Move", op).invoke();
return true;
}
/*-------------------------------------------------------------------------------------*/
}