blob: 369301ab1e768702ede47eb53654ae71a59d34af [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 com.intellij.ide.projectView.impl;
import com.intellij.ide.DataManager;
import com.intellij.ide.dnd.DnDEvent;
import com.intellij.ide.dnd.DnDNativeTarget;
import com.intellij.ide.dnd.FileCopyPasteUtil;
import com.intellij.ide.dnd.TransferableWrapper;
import com.intellij.ide.projectView.impl.nodes.DropTargetNode;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringActionHandlerFactory;
import com.intellij.refactoring.actions.BaseRefactoringAction;
import com.intellij.refactoring.copy.CopyHandler;
import com.intellij.refactoring.move.MoveHandler;
import com.intellij.ui.awt.RelativeRectangle;
import com.intellij.util.ArrayUtilRt;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.dnd.DnDConstants;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @author Anna
* @author Konstantin Bulenkov
*/
class ProjectViewDropTarget implements DnDNativeTarget {
private final JTree myTree;
private final Retriever myRetriever;
private final Project myProject;
ProjectViewDropTarget(JTree tree, Retriever retriever, Project project) {
myTree = tree;
myRetriever = retriever;
myProject = project;
}
@Override
public boolean update(DnDEvent event) {
event.setDropPossible(false, "");
final Object attached = event.getAttachedObject();
final int dropAction = event.getAction().getActionId();
final DropHandler dropHandler = getDropHandler(dropAction);
final TreeNode[] sourceNodes = getSourceNodes(attached);
final Point point = event.getPoint();
final TreeNode targetNode = getTargetNode(point);
if (targetNode == null || (dropAction & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
return false;
}
else if (sourceNodes == null && !FileCopyPasteUtil.isFileListFlavorAvailable(event)) {
return false;
}
else if (sourceNodes != null && ArrayUtilRt.find(sourceNodes, targetNode) != -1) {
return false;
}
else if (sourceNodes != null && !dropHandler.isValidSource(sourceNodes, targetNode)) {
return false;
}
if (sourceNodes != null) {
boolean redundant = true;
for (TreeNode sourceNode : sourceNodes) {
if (!dropHandler.isDropRedundant(sourceNode, targetNode)) {
redundant = false;
break;
}
}
if (redundant) return false;
}
else {
// it seems like it's not possible to obtain dragged items _before_ accepting _drop_ on Macs, so just skip this check
if (!SystemInfo.isMac) {
final PsiFileSystemItem[] psiFiles = getPsiFiles(FileCopyPasteUtil.getFileListFromAttachedObject(attached));
if (psiFiles == null || psiFiles.length == 0) return false;
if (!MoveHandler.isValidTarget(getPsiElement(targetNode), psiFiles)) return false;
}
}
final Rectangle pathBounds = myTree.getPathBounds(myTree.getClosestPathForLocation(point.x, point.y));
event.setHighlighting(new RelativeRectangle(myTree, pathBounds), DnDEvent.DropTargetHighlightingType.RECTANGLE);
event.setDropPossible(true);
return false;
}
@Override
public void drop(DnDEvent event) {
final Object attached = event.getAttachedObject();
final TreeNode[] sourceNodes = getSourceNodes(attached);
final TreeNode targetNode = getTargetNode(event.getPoint());
assert targetNode != null;
final int dropAction = event.getAction().getActionId();
if (sourceNodes == null) {
if (FileCopyPasteUtil.isFileListFlavorAvailable(event)) {
List<File> fileList = FileCopyPasteUtil.getFileListFromAttachedObject(attached);
if (!fileList.isEmpty()) {
getDropHandler(dropAction).doDropFiles(fileList, targetNode);
}
}
}
else {
doDrop(sourceNodes, targetNode, dropAction);
}
}
@Override
public void cleanUpOnLeave() {
}
@Override
public void updateDraggedImage(Image image, Point dropPoint, Point imageOffset) {
}
@Nullable
private static TreeNode[] getSourceNodes(final Object transferData) {
if (transferData instanceof TransferableWrapper) {
return ((TransferableWrapper)transferData).getTreeNodes();
}
return null;
}
@Nullable
private TreeNode getTargetNode(final Point location) {
final TreePath path = myTree.getClosestPathForLocation(location.x, location.y);
return path == null ? null : (TreeNode)path.getLastPathComponent();
}
private boolean doDrop(@NotNull final TreeNode[] sourceNodes,
@NotNull final TreeNode targetNode,
final int dropAction) {
TreeNode validTargetNode = getValidTargetNode(sourceNodes, targetNode, dropAction);
if (validTargetNode != null) {
final TreeNode[] filteredSourceNodes = removeRedundantSourceNodes(sourceNodes, validTargetNode, dropAction);
if (filteredSourceNodes.length != 0) {
getDropHandler(dropAction).doDrop(filteredSourceNodes, validTargetNode);
return true;
}
}
return false;
}
@Nullable
private TreeNode getValidTargetNode(final @NotNull TreeNode[] sourceNodes, final @NotNull TreeNode targetNode, final int dropAction) {
final DropHandler dropHandler = getDropHandler(dropAction);
TreeNode currentNode = targetNode;
while (true) {
if (dropHandler.isValidTarget(sourceNodes, currentNode)) {
return currentNode;
}
if (!dropHandler.shouldDelegateToParent(sourceNodes, currentNode)) return null;
currentNode = currentNode.getParent();
if (currentNode == null) return null;
}
}
private TreeNode[] removeRedundantSourceNodes(@NotNull final TreeNode[] sourceNodes,
@NotNull final TreeNode targetNode,
final int dropAction) {
final DropHandler dropHandler = getDropHandler(dropAction);
List<TreeNode> result = new ArrayList<TreeNode>(sourceNodes.length);
for (TreeNode sourceNode : sourceNodes) {
if (!dropHandler.isDropRedundant(sourceNode, targetNode)) {
result.add(sourceNode);
}
}
return result.toArray(new TreeNode[result.size()]);
}
public DropHandler getDropHandler(final int dropAction) {
return (dropAction == DnDConstants.ACTION_COPY) ? new CopyDropHandler() : new MoveDropHandler();
}
private interface DropHandler {
boolean isValidSource(@NotNull TreeNode[] sourceNodes, TreeNode targetNode);
boolean isValidTarget(@NotNull TreeNode[] sourceNodes, @NotNull TreeNode targetNode);
boolean shouldDelegateToParent(TreeNode[] sourceNodes, @NotNull TreeNode targetNode);
boolean isDropRedundant(@NotNull TreeNode sourceNode, @NotNull TreeNode targetNode);
void doDrop(@NotNull TreeNode[] sourceNodes, @NotNull TreeNode targetNode);
void doDropFiles(List<File> fileList, TreeNode targetNode);
}
@Nullable
protected PsiElement getPsiElement(@Nullable final TreeNode treeNode) {
return myRetriever.getPsiElement(treeNode);
}
protected Module getModule(@Nullable final TreeNode treeNode) {
return myRetriever.getModule(treeNode);
}
public abstract class MoveCopyDropHandler implements DropHandler {
@Override
public boolean isValidSource(@NotNull final TreeNode[] sourceNodes, TreeNode targetNode) {
return canDrop(sourceNodes, targetNode);
}
@Override
public boolean isValidTarget(@NotNull final TreeNode[] sourceNodes, final @NotNull TreeNode targetNode) {
return canDrop(sourceNodes, targetNode);
}
protected abstract boolean canDrop(@NotNull TreeNode[] sourceNodes, @Nullable TreeNode targetNode);
@NotNull
protected PsiElement[] getPsiElements(@NotNull TreeNode[] nodes) {
List<PsiElement> psiElements = new ArrayList<PsiElement>(nodes.length);
for (TreeNode node : nodes) {
PsiElement psiElement = getPsiElement(node);
if (psiElement != null) {
psiElements.add(psiElement);
}
}
if (psiElements.size() != 0) {
return PsiUtilCore.toPsiElementArray(psiElements);
}
else {
return BaseRefactoringAction.getPsiElementArray(DataManager.getInstance().getDataContext(myTree));
}
}
}
@Nullable
protected PsiFileSystemItem[] getPsiFiles(@Nullable List<File> fileList) {
if (fileList == null) return null;
List<PsiFileSystemItem> sourceFiles = new ArrayList<PsiFileSystemItem>();
for (File file : fileList) {
final VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
if (vFile != null) {
final PsiFileSystemItem psiFile = vFile.isDirectory()
? PsiManager.getInstance(myProject).findDirectory(vFile)
: PsiManager.getInstance(myProject).findFile(vFile);
if (psiFile != null) {
sourceFiles.add(psiFile);
}
}
}
return sourceFiles.toArray(new PsiFileSystemItem[sourceFiles.size()]);
}
private class MoveDropHandler extends MoveCopyDropHandler {
@Override
protected boolean canDrop(@NotNull final TreeNode[] sourceNodes, @Nullable final TreeNode targetNode) {
if (targetNode instanceof DefaultMutableTreeNode) {
final Object userObject = ((DefaultMutableTreeNode)targetNode).getUserObject();
if (userObject instanceof DropTargetNode && ((DropTargetNode)userObject).canDrop(sourceNodes)) {
return true;
}
}
final PsiElement[] sourceElements = getPsiElements(sourceNodes);
final PsiElement targetElement = getPsiElement(targetNode);
return sourceElements.length == 0 ||
((targetNode == null || targetElement != null) &&
MoveHandler.canMove(sourceElements, targetElement));
}
@Override
public void doDrop(@NotNull final TreeNode[] sourceNodes, @NotNull final TreeNode targetNode) {
if (targetNode instanceof DefaultMutableTreeNode) {
final Object userObject = ((DefaultMutableTreeNode)targetNode).getUserObject();
if (userObject instanceof DropTargetNode && ((DropTargetNode)userObject).canDrop(sourceNodes)) {
final DataContext dataContext = DataManager.getInstance().getDataContext(myTree);
((DropTargetNode)userObject).drop(sourceNodes, dataContext);
}
}
final PsiElement[] sourceElements = getPsiElements(sourceNodes);
doDrop(targetNode, sourceElements, false);
}
private void doDrop(TreeNode targetNode, PsiElement[] sourceElements, final boolean externalDrop) {
final PsiElement targetElement = getPsiElement(targetNode);
if (targetElement == null) return;
final Module module = getModule(targetNode);
final DataContext dataContext = DataManager.getInstance().getDataContext(myTree);
getActionHandler().invoke(myProject, sourceElements, new DataContext() {
@Override
@Nullable
public Object getData(@NonNls String dataId) {
if (LangDataKeys.TARGET_MODULE.is(dataId)) {
if (module != null) return module;
}
if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) {
return targetElement;
}
else {
return externalDrop ? null : dataContext.getData(dataId);
}
}
});
}
private RefactoringActionHandler getActionHandler() {
return RefactoringActionHandlerFactory.getInstance().createMoveHandler();
}
@Override
public boolean isDropRedundant(@NotNull TreeNode sourceNode, @NotNull TreeNode targetNode) {
return sourceNode.getParent() == targetNode || MoveHandler.isMoveRedundant(getPsiElement(sourceNode), getPsiElement(targetNode));
}
@Override
public boolean shouldDelegateToParent(TreeNode[] sourceNodes, @NotNull final TreeNode targetNode) {
final PsiElement psiElement = getPsiElement(targetNode);
return !MoveHandler.isValidTarget(psiElement, getPsiElements(sourceNodes));
}
@Override
public void doDropFiles(List<File> fileList, TreeNode targetNode) {
final PsiFileSystemItem[] sourceFileArray = getPsiFiles(fileList);
if (targetNode instanceof DefaultMutableTreeNode) {
final Object userObject = ((DefaultMutableTreeNode)targetNode).getUserObject();
if (userObject instanceof DropTargetNode) {
final DataContext dataContext = DataManager.getInstance().getDataContext(myTree);
((DropTargetNode)userObject).dropExternalFiles(sourceFileArray, dataContext);
return;
}
}
doDrop(targetNode, sourceFileArray, true);
}
}
private class CopyDropHandler extends MoveCopyDropHandler {
@Override
protected boolean canDrop(@NotNull final TreeNode[] sourceNodes, @Nullable final TreeNode targetNode) {
final PsiElement[] sourceElements = getPsiElements(sourceNodes);
final PsiElement targetElement = getPsiElement(targetNode);
if (targetElement == null) return false;
final PsiFile containingFile = targetElement.getContainingFile();
final boolean isTargetAcceptable = targetElement instanceof PsiDirectoryContainer ||
targetElement instanceof PsiDirectory ||
(containingFile != null && containingFile.getContainingDirectory() != null);
return isTargetAcceptable && CopyHandler.canCopy(sourceElements);
}
@Override
public void doDrop(@NotNull final TreeNode[] sourceNodes, @NotNull final TreeNode targetNode) {
final PsiElement[] sourceElements = getPsiElements(sourceNodes);
doDrop(targetNode, sourceElements);
}
private void doDrop(TreeNode targetNode, PsiElement[] sourceElements) {
final PsiElement targetElement = getPsiElement(targetNode);
if (targetElement == null) return;
final PsiDirectory psiDirectory;
if (targetElement instanceof PsiDirectoryContainer) {
final PsiDirectoryContainer directoryContainer = (PsiDirectoryContainer)targetElement;
final PsiDirectory[] psiDirectories = directoryContainer.getDirectories();
psiDirectory = psiDirectories.length != 0 ? psiDirectories[0] : null;
}
else if (targetElement instanceof PsiDirectory) {
psiDirectory = (PsiDirectory)targetElement;
}
else {
final PsiFile containingFile = targetElement.getContainingFile();
LOG.assertTrue(containingFile != null);
psiDirectory = containingFile.getContainingDirectory();
}
CopyHandler.doCopy(sourceElements, psiDirectory);
}
@Override
public boolean isDropRedundant(@NotNull TreeNode sourceNode, @NotNull TreeNode targetNode) {
return false;
}
@Override
public boolean shouldDelegateToParent(TreeNode[] sourceNodes, @NotNull final TreeNode targetNode) {
final PsiElement psiElement = getPsiElement(targetNode);
return psiElement == null || (!(psiElement instanceof PsiDirectoryContainer) && !(psiElement instanceof PsiDirectory));
}
@Override
public void doDropFiles(List<File> fileList, TreeNode targetNode) {
final PsiFileSystemItem[] sourceFileArray = getPsiFiles(fileList);
doDrop(targetNode, sourceFileArray);
}
}
}