| /* |
| * 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); |
| } |
| } |
| } |