/* * Copyright 2003-2016 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 jetbrains.mps.ide.projectPane; import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.DataContext; import jetbrains.mps.ide.ui.tree.MPSTreeNode; import jetbrains.mps.ide.ui.tree.smodel.PackageNode; import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode; import jetbrains.mps.ide.ui.tree.smodel.SNodeGroupTreeNode; import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations; import jetbrains.mps.plugins.projectplugins.ProjectPluginManager; import jetbrains.mps.plugins.relations.RelationDescriptor; import jetbrains.mps.project.MPSProject; import jetbrains.mps.smodel.SNodeUtil; import jetbrains.mps.util.EqualUtil; import jetbrains.mps.util.NameUtil; import jetbrains.mps.util.Pair; import jetbrains.mps.workbench.MPSDataKeys; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeAccessUtil; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.module.SRepository; import javax.swing.JOptionPane; import javax.swing.JTree; import javax.swing.tree.TreePath; import java.awt.Frame; import java.awt.Point; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ProjectPaneDnDListener implements DropTargetListener { private final JTree myTree; private final DataFlavor myDataFlavor; public ProjectPaneDnDListener(final JTree tree, DataFlavor dataFlavor) { myDataFlavor = dataFlavor; myTree = tree; } @Override public void dragEnter(DropTargetDragEvent dtde) { dtde.acceptDrag(dtde.getDropAction()); } @Override public void dragOver(DropTargetDragEvent dtde) { MPSTreeNode target = getTargetTreeNode(dtde); List<Pair<SNodeReference, String>> sourceNodes = getSourceNodes(dtde); if (isDropTargetAcceptable(target, sourceNodes)) dtde.acceptDrag(dtde.getDropAction()); else dtde.rejectDrag(); } @Override public void dropActionChanged(DropTargetDragEvent dtde) { } @Override public void dragExit(DropTargetEvent dte) { } @Override public void drop(final DropTargetDropEvent dtde) { final MPSTreeNode target = getTargetTreeNode(dtde); final List<Pair<SNodeReference, String>> sourceNodes = getSourceNodes(dtde); if (!isDropTargetAcceptable(target, sourceNodes)) { dtde.rejectDrop(); return; } dtde.acceptDrop(dtde.getDropAction()); final DataContext dataContext = DataManager.getInstance().getDataContext(myTree); final MPSProject project = MPSDataKeys.MPS_PROJECT.getData(dataContext); if (project == null) { dtde.rejectDrop(); return; } Frame frame = MPSDataKeys.FRAME.getData(dataContext); final String targetPackage = (getTargetVirtualPackage(target) == null) ? "" : getTargetVirtualPackage(target); String text = getConfirmLabel(sourceNodes.size(), targetPackage); int result = JOptionPane.showConfirmDialog(frame, text, "Move Nodes", JOptionPane.YES_NO_OPTION); if (result != JOptionPane.YES_OPTION) return; project.getModelAccess().executeCommand(new Runnable() { @Override public void run() { SModel targetModel = getTargetModel(target); if (targetModel == null) return; for (Pair<SNode, String> sourceNode : getNodesToMove(targetModel, targetPackage, sourceNodes)) { String fullTargetPack = getFullTargetPack(targetPackage, sourceNode.o2); SNodeAccessUtil.setProperty(sourceNode.o1, SNodeUtil.property_BaseConcept_virtualPackage, fullTargetPack); if (SNodeOperations.isInstanceOf(sourceNode.o1, SNodeUtil.concept_AbstractConceptDeclaration)) { SNode baseNode = sourceNode.o1; List<RelationDescriptor> tabs = ProjectPluginManager.getApplicableTabs(project.getProject(), baseNode); for (RelationDescriptor tab : tabs) { if (!tab.isApplicable(baseNode)) continue; for (SNode aspect : tab.getNodes(baseNode)) { if (tab.getBaseNode(aspect) != baseNode) continue; SNodeAccessUtil.setProperty(aspect, SNodeUtil.property_BaseConcept_virtualPackage, fullTargetPack); } } } } } }); } //must return empty list in case no nodes are available @NotNull private List<Pair<SNodeReference, String>> getSourceNodes(DropTargetEvent dtde) { try { Transferable transferable = null; if (dtde instanceof DropTargetDropEvent) { transferable = ((DropTargetDropEvent) dtde).getTransferable(); } else if (dtde instanceof DropTargetDragEvent) { transferable = ((DropTargetDragEvent) dtde).getTransferable(); } if (transferable == null) return Collections.emptyList(); Object source = transferable.getTransferData(myDataFlavor); if (source instanceof List) { return (List<Pair<SNodeReference, String>>) source; } return Collections.emptyList(); } catch (UnsupportedFlavorException e) { return Collections.emptyList(); } catch (IOException e) { Logger.getLogger(ProjectPaneDnDListener.class).error(e.toString(), e); return Collections.emptyList(); } } private MPSTreeNode getTargetTreeNode(DropTargetEvent dtde) { Point point; if (dtde instanceof DropTargetDropEvent) point = ((DropTargetDropEvent) dtde).getLocation(); else if (dtde instanceof DropTargetDragEvent) point = ((DropTargetDragEvent) dtde).getLocation(); else return null; final TreePath treePath = myTree.getPathForLocation(point.x, point.y); if (treePath == null) return null; Object target = treePath.getLastPathComponent(); if (!(target instanceof MPSTreeNode)) return null; return (MPSTreeNode) target; } private boolean isDropTargetAcceptable(MPSTreeNode treeNode, @NotNull List<Pair<SNodeReference, String>> srcNodes) { // check all nodes from the same model and drop target is folder in that model or model itself SModelReference srcModelRef = null; for (Pair<SNodeReference, String> srcNode : srcNodes) { if (srcModelRef == null) { srcModelRef = srcNode.o1.getModelReference(); } else if (!srcModelRef.equals(srcNode.o1.getModelReference())) { return false; } } if (srcModelRef == null) return false; // empty list is not acceptable if (treeNode instanceof SModelTreeNode) { return srcModelRef.equals(((SModelTreeNode) treeNode).getModel().getReference()); } else if (treeNode instanceof SNodeGroupTreeNode) { return srcModelRef.equals(((SNodeGroupTreeNode) treeNode).getModelReference()); } return false; } private List<Pair<SNode, String>> getNodesToMove(@NotNull SModel targetModel, String virtualPackage, List<Pair<SNodeReference, String>> sourceNodes) { final SRepository repo = targetModel.getRepository(); List<Pair<SNode, String>> result = new ArrayList<Pair<SNode, String>>(); for (final Pair<SNodeReference, String> node : sourceNodes) { SNode snode = node.o1.resolve(repo); if (snode == null) continue; if (EqualUtil.equals(virtualPackage + node.o2, getVirtualPackage(snode))) continue; SModel sourceModel = snode.getModel(); if (EqualUtil.equals(sourceModel, targetModel)) { result.add(new Pair<SNode, String>(snode, node.o2)); } } return result; } private String getVirtualPackage(final SNode node) { String result = SNodeAccessUtil.getProperty(node, SNodeUtil.property_BaseConcept_virtualPackage); return (result == null) ? "" : result; } private String getTargetVirtualPackage(MPSTreeNode node) { while (node != null && !(node instanceof PackageNode)) { node = (MPSTreeNode) node.getParent(); } if (node != null) { String result = ((PackageNode) node).getFullPackage(); return (result == null) ? "" : result; } return null; } private SModel getTargetModel(MPSTreeNode node) { while (node != null && !(node instanceof SModelTreeNode)) { node = (MPSTreeNode) node.getParent(); } if (node != null) { return ((SModelTreeNode) node).getModel(); } return null; } private String getPackagePresentation(String name) { return (name == null || name.isEmpty()) ? "<i><root></i>" : "'<b>" + name + "</b>'"; } private String getFullTargetPack(String targetPackage, String basePack) { return (basePack == null || basePack.isEmpty()) ? targetPackage : targetPackage + "." + basePack; } private String getConfirmLabel(int size, String target) { return String.format("<html>Do you want to move %s to %s?</html>", NameUtil.formatNumericalString(size, "node"), getPackagePresentation(target)); } }