/*
* 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.project;
import com.intellij.ide.util.gotoByName.ChooseByNamePopup;
import com.intellij.ide.util.gotoByName.ChooseByNamePopupComponent;
import com.intellij.openapi.actionSystem.ShortcutSet;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.TransactionGuard;
import jetbrains.mps.FilteredGlobalScope;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.scope.ConditionalScope;
import jetbrains.mps.smodel.SModelStereotype;
import jetbrains.mps.smodel.undo.DefaultCommand;
import jetbrains.mps.smodel.undo.NodeBasedCommand;
import jetbrains.mps.util.Callback;
import jetbrains.mps.util.NotCondition;
import jetbrains.mps.workbench.choose.ChooseByNameData;
import jetbrains.mps.workbench.choose.ModelScopeIterable;
import jetbrains.mps.workbench.choose.ModelsPresentation;
import jetbrains.mps.workbench.choose.NavigationTargetPresentation;
import jetbrains.mps.workbench.choose.NavigationTargetScopeIterable;
import jetbrains.mps.workbench.goTo.ui.MpsPopupFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.module.SRepository;
import org.jetbrains.mps.openapi.module.SearchScope;
import org.jetbrains.mps.openapi.persistence.NavigationParticipant.NavigationTarget;
import org.jetbrains.mps.util.Condition;
import java.awt.Frame;
/**
* Facility to interoperate with user to add a new model dependency to a model.
* Responsible to collect user input and to update model internals.
*
* @author Artem Tikhomirov
* @since 3.4
*/
public class ModelImportHelper {
private final MPSProject myProject;
private ShortcutSet myShortcut;
private String myInitialText;
private SNode myContextNode;
public ModelImportHelper(@NotNull MPSProject project) {
myProject = project;
}
/**
* Override keyboard shortcut for model pick dialog to switch between global and package scope (e.g. to match that of invoking action).
* <p/>
*
* @return <code>this</code> for convenience
*/
public ModelImportHelper setShortcut(@Nullable ShortcutSet shortcut) {
myShortcut = shortcut;
return this;
}
/**
* @param initialText Text to start selection dialog with
* @return <code>this</code> for convenience
*/
public ModelImportHelper setInitialText(@Nullable String initialText) {
myInitialText = initialText;
return this;
}
/**
* @param contextNode will be used to execute {@link jetbrains.mps.smodel.undo.NodeBasedCommand}
* with this node used as a contextual
* @return <code>this</code> for convenience
*/
public ModelImportHelper setContextNode(SNode contextNode) {
myContextNode = contextNode;
return this;
}
/**
* Ask user to select a model and import it
*
* @param model model to add import to
*/
public void addImport(@NotNull SModel model) {
// FIXME identical condition in GoToModel_Action and in GoToModelPlatformAction
Condition<SModel> cond = new Condition<SModel>() {
@Override
public boolean met(SModel modelDescriptor) {
boolean rightStereotype = SModelStereotype.isUserModel(modelDescriptor)
|| SModelStereotype.isStubModel(modelDescriptor);
boolean hasModule = modelDescriptor.getModule() != null;
return rightStereotype && hasModule;
}
};
ConditionalScope localScope = new ConditionalScope(myProject.getScope(), null, cond);
ConditionalScope globalScope = new ConditionalScope(new FilteredGlobalScope(), null, cond);
SRepository repo = myProject.getRepository();
ChooseByNameData<SModelReference> gotoData = new ChooseByNameData<>(new ModelsPresentation(repo));
gotoData.derivePrompts("model").setPrompts("Import model:", gotoData.getNotFoundMessage(), gotoData.getNotInMessage());
gotoData.setScope(new ModelScopeIterable(localScope, repo), new ModelScopeIterable(globalScope, repo));
ChooseByNamePopup popup = MpsPopupFactory.createPackagePopup(myProject, gotoData, myInitialText);
if (myShortcut != null) {
popup.setCheckBoxShortcut(myShortcut);
}
popup.invoke(new AddImportCallback(myProject, model, myContextNode) {
@Override
public void elementChosen(Object element) {
if (element instanceof SModelReference) {
runImportCommand((SModelReference) element);
}
}
}, ModalityState.current(), false);
}
public void addImportByRoot(@NotNull SModel model, final Callback<String> importedRootCallback) {
ChooseByNameData<NavigationTarget> gotoData = new ChooseByNameData<>(new NavigationTargetPresentation());
gotoData.derivePrompts("node").setPrompts("Import model that contains root:", gotoData.getNotFoundMessage(), gotoData.getNotInMessage());
gotoData.setCheckBoxName("Include stub and non-project models");
ConditionalScope localScope = new ConditionalScope(myProject.getScope(), null, NotCondition.negate(SModelStereotype::isStubModel));
SearchScope globalScope = GlobalScope.getInstance();
final SRepository repo = myProject.getRepository();
gotoData.setScope(new NavigationTargetScopeIterable(localScope, repo), new NavigationTargetScopeIterable(globalScope, repo));
ChooseByNamePopup popup = MpsPopupFactory.createNodePopup(myProject.getProject(), gotoData, myInitialText, null);
if (myShortcut != null) {
popup.setCheckBoxShortcut(myShortcut);
}
popup.invoke(new AddImportCallback<String>(myProject, model, myContextNode) {
@Override
public void elementChosen(Object element) {
if (element instanceof NavigationTarget) {
NavigationTarget object = (NavigationTarget) element;
runImportCommand(object.getNodeReference().getModelReference(), object.getPresentation());
}
}
@Override
public void executeCallback(String... parameters) {
importedRootCallback.call(parameters[0]);
}
}, ModalityState.current(), false);
}
// Callback.elementChosen shall populate myModelToImport
private static abstract class AddImportCallback<T> extends ChooseByNamePopupComponent.Callback {
private final jetbrains.mps.project.Project myProject;
private final SModel myModel;
private final SNode myContextNode;
public AddImportCallback(jetbrains.mps.project.Project mpsProject, SModel model, SNode contextNode) {
myProject = mpsProject;
myModel = model;
myContextNode = contextNode;
}
/*package*/ Frame getFrame() {
return ProjectHelper.toMainFrame(myProject);
}
protected void runImportCommand(final SModelReference modelToImport, T... callbackParameters) {
TransactionGuard.getInstance().submitTransactionAndWait(() -> {
// Have to extract preparation of ModelImporter outside command,
// because ModelImporter#confirmModuleChanges require Swing, which is not allowed inside command.
final ModelImporter modelImporter = new ModelImporter(myModel);
myProject.getModelAccess().runReadAction(() -> modelImporter.prepare(modelToImport));
final boolean confirmed = !modelImporter.affectsModuleDependencies() || modelImporter.confirmModuleChanges(getFrame());
Runnable command;
if (myContextNode != null) {
command = new NodeBasedCommand(myContextNode) {
@Override
public void run() {
if (confirmed) {
modelImporter.execute();
}
executeCallback(callbackParameters);
}
};
} else {
command = new DefaultCommand() {
@Override
public void run() {
if (confirmed) {
modelImporter.execute();
}
executeCallback(callbackParameters);
}
};
}
myProject.getModelAccess().executeCommand(command);
});
}
public void executeCallback(T... parameters) {
}
}
}