/* * 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.idea.core.actions; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataKey; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import jetbrains.mps.icons.MPSIcons.Nodes; import jetbrains.mps.ide.actions.MPSCommonDataKeys; import jetbrains.mps.ide.icons.GlobalIconManager; import jetbrains.mps.ide.icons.IdeIcons; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.idea.core.MPSBundle; import jetbrains.mps.idea.core.project.module.ModuleMPSSupport; import jetbrains.mps.idea.core.ui.CreateFromTemplateDialog; import jetbrains.mps.nodefs.NodeVirtualFileSystem; import jetbrains.mps.smodel.SModelOperations; import jetbrains.mps.smodel.SNodeUtil; import jetbrains.mps.smodel.action.NodeFactoryManager; import jetbrains.mps.smodel.constraints.ModelConstraints; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.language.SAbstractConcept; import org.jetbrains.mps.openapi.language.SLanguage; import org.jetbrains.mps.openapi.model.EditableSModel; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeAccessUtil; import org.jetbrains.mps.openapi.model.SNodeReference; import javax.swing.Icon; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; public class NewRootAction extends AnAction { private EditableSModel myModel; private Project myProject; private Map<String, SAbstractConcept> myConceptFqNameToNodePointerMap = new LinkedHashMap<>(); public NewRootAction() { super(MPSBundle.message("new.root.action"), null, IdeIcons.DEFAULT_ROOT_ICON); } @Override public void actionPerformed(AnActionEvent e) { Interaction interaction = ApplicationManager.getApplication().isUnitTestMode() ? e.getData(HEADLESS_INTERACTION) : new UiInteraction(); assert interaction != null; Pair<String, SAbstractConcept> choice = interaction.choose(myConceptFqNameToNodePointerMap); if (choice == null) { // cancelled return; } String name = choice.first; SAbstractConcept concept = choice.second; Ref<SNodeReference> createdNode = new Ref<>(); ProjectHelper.getModelAccess(myProject).executeCommand(() -> { SNode newNode = NodeFactoryManager.createNode(concept, null, null, myModel); SNodeAccessUtil.setProperty(newNode, SNodeUtil.property_INamedConcept_name, name); myModel.addRootNode(newNode); myModel.save(); createdNode.set(newNode.getReference()); }); if (!createdNode.isNull() && !ApplicationManager.getApplication().isUnitTestMode()) { FileEditorManager.getInstance(myProject).openFile( NodeVirtualFileSystem.getInstance().getFileFor(ProjectHelper.getProjectRepository(myProject), createdNode.get()), true); } } @Override public void update(AnActionEvent e) { updateFields(e); boolean isVisible = myProject != null && myModel != null; e.getPresentation().setVisible(isVisible); e.getPresentation().setEnabled(!myConceptFqNameToNodePointerMap.isEmpty()); if (!isVisible) return; e.getPresentation().setText(MPSBundle.message("new.root.action")); e.getPresentation().setIcon(IdeIcons.DEFAULT_ROOT_ICON); } private void updateFields(AnActionEvent e) { // cleaning all local fields myModel = null; myConceptFqNameToNodePointerMap.clear(); myProject = e.getData(PlatformDataKeys.PROJECT); final SModel model = MPSCommonDataKeys.CONTEXT_MODEL.getData(e.getDataContext()); if (model != null && model.isReadOnly()) { return; } if (model instanceof EditableSModel) { myModel = (EditableSModel) model; } if (myProject == null || myModel == null) { return; } final jetbrains.mps.project.Project mpsProject = ProjectHelper.fromIdeaProject(myProject); if (mpsProject == null) { return; } final Module module = e.getData(LangDataKeys.MODULE); final VirtualFile[] vFiles = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); if (module == null || vFiles == null || vFiles.length != 1) { return; } final ModuleMPSSupport mpsFacade = ModuleMPSSupport.getInstance(); if (mpsFacade == null || !mpsFacade.isMPSEnabled(module)) { return; } String url = vFiles[0].getUrl(); if (!LocalFileSystem.PROTOCOL.equals(VirtualFileManager.extractProtocol(url))) { return; } mpsProject.getModelAccess().runReadAction(() -> { Set<SLanguage> modelLanguages = SModelOperations.getAllLanguageImports(model); for (SLanguage language : modelLanguages) { for (SAbstractConcept concept : language.getConcepts()) { if (ModelConstraints.canBeRoot(concept, model)) { myConceptFqNameToNodePointerMap.put(concept.getQualifiedName(), concept); } } } }); } private class UiInteraction implements Interaction { @Override public Pair<String, SAbstractConcept> choose(Map<String, SAbstractConcept> concepts) { MyCreateFromTemplateDialog dialog = new MyCreateFromTemplateDialog(myProject, concepts); dialog.setTitle(MPSBundle.message("create.new.root.dialog.title")); ProjectHelper.getModelAccess(myProject).runReadAction(() -> { for (Map.Entry<String, SAbstractConcept> entry : concepts.entrySet()) { String conceptFqName = entry.getKey(); SAbstractConcept concept = entry.getValue(); final GlobalIconManager globalIconManager = ApplicationManager.getApplication().getComponent(GlobalIconManager.class); final Icon conceptIcon = globalIconManager == null ? Nodes.RootNode : globalIconManager.getIconFor(concept); dialog.getKindCombo().addItem(concept.getConceptAlias(), conceptIcon, conceptFqName); dialog.setTemplateKindComponentsVisible(true); } }); dialog.show(); return dialog.getResult(); } } private class MyCreateFromTemplateDialog extends CreateFromTemplateDialog { private Pair<String, SAbstractConcept> myResult = null; private Map<String, SAbstractConcept> myConcepts; protected MyCreateFromTemplateDialog(@NotNull Project project, @NotNull Map<String, SAbstractConcept> concepts) { super(project); myConcepts = concepts; } public Pair<String, SAbstractConcept> getResult() { return myResult; } @Override protected void doOKAction() { String name = getNameField().getText(); SAbstractConcept concept = myConcepts.get(getKindCombo().getSelectedName()); myResult = Pair.create(name, concept); super.doOKAction(); } } public static final DataKey<Interaction> HEADLESS_INTERACTION = DataKey.create("newRootActionHeadlessInteraction"); public interface Interaction { /** * @param concepts List of concepts that the dialog has to offer to new node creation * @return Name and concept (from the map param) for the node to create; or null if cancelled */ Pair<String, SAbstractConcept> choose(Map<String, SAbstractConcept> concepts); } }