/*
* Copyright 2003-2013 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.workbench.dialogs.project.newproject;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.ListItemDescriptor;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.CollectionListModel;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.HideableDecorator;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.JBSplitter;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.SearchTextField;
import com.intellij.ui.components.JBList;
import com.intellij.ui.popup.list.GroupedItemsListRenderer;
import com.intellij.ui.speedSearch.FilteringListModel;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.util.containers.SortedList;
import com.intellij.util.ui.JBInsets;
import com.intellij.util.ui.JBUI;
import jetbrains.mps.ide.ui.util.UIUtil;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.project.MPSProject;
import jetbrains.mps.workbench.dialogs.project.newproject.ProjectFactory.ProjectNotCreatedException;
import org.jetbrains.annotations.Nullable;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
public class CreateProjectWizard extends DialogWrapper {
private static final String PROJECTS_DIR = System.getProperty("user.home") + File.separator + "MPSProjects";
private Project myCurrentProject;
private JPanel myPanel;
private JBSplitter mySplitter;
private JPanel myLeftPanel;
private SearchTextField mySearchField;
private JBList myList;
private FilteringListModel<TemplateItem> myFilteringListModel;
private JPanel myRightPanel;
private JPanel myProjectPanel;
private JTextField myProjectName;
private PathField myProjectPath;
private JPanel myDescriptionPanel;
private JTextPane myDescriptionPane;
private JPanel myTemplateSettingsHolder;
private HideableDecorator myHideableDecorator;
private JPanel myTemplateSettings;
private TemplateItem myCurrentTemplateItem = null;
private ProjectFormatPanel myProjectFormatPanel = new ProjectFormatPanel();
public CreateProjectWizard(@Nullable Project project) {
super(project);
myCurrentProject = project;
setTitle("New Project");
setOKButtonText("&OK");
setCancelButtonText("Ca&ncel");
init();
}
@Nullable
@Override
protected JComponent createCenterPanel() {
if (myPanel == null) {
myPanel = new JPanel(new GridLayoutManager(1, 1, JBUI.emptyInsets(), -1, -1));
initLeftPanel();
initRightPanel();
mySplitter = new JBSplitter(false, 0.3f, 0.3f, 0.6f);
mySplitter.setSplitterProportionKey("select.template.proportion");
mySplitter.setFirstComponent(myLeftPanel);
mySplitter.setSecondComponent(myRightPanel);
myPanel.add(mySplitter,
new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_CAN_SHRINK,
GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_CAN_SHRINK, null, null, null));
myPanel.setPreferredSize(new Dimension(600, 400));
myList.setSelectedIndex(0);
}
return myPanel;
}
private void initLeftPanel() {
myLeftPanel = new JPanel(new GridLayoutManager(2, 1, JBUI.emptyInsets(), -1, -1));
mySearchField = new SearchTextField(false);
mySearchField.addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
TemplateItem tmp = myCurrentTemplateItem;
myFilteringListModel.refilter();
int idx = myFilteringListModel.getElementIndex(tmp);
myList.setSelectedIndex(idx < 0 ? 0 : idx);
}
});
new AnAction() {
@Override
public void actionPerformed(AnActionEvent e) {
InputEvent event = e.getInputEvent();
if (event instanceof KeyEvent) {
int row = myList.getSelectedIndex();
int toSelect;
switch (((KeyEvent) event).getKeyCode()) {
case KeyEvent.VK_UP:
toSelect = row == 0 ? myList.getItemsCount() - 1 : row - 1;
myList.setSelectedIndex(toSelect);
myList.ensureIndexIsVisible(toSelect);
break;
case KeyEvent.VK_DOWN:
toSelect = row < myList.getItemsCount() - 1 ? row + 1 : 0;
myList.setSelectedIndex(toSelect);
myList.ensureIndexIsVisible(toSelect);
break;
}
}
}
}.registerCustomShortcutSet(new CustomShortcutSet(KeyEvent.VK_UP, KeyEvent.VK_DOWN), mySearchField);
myLeftPanel.add(mySearchField,
new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK,
GridConstraints.SIZEPOLICY_FIXED, null, null, null));
myList = new JBList();
List<ProjectTemplatesGroup> templatesGroups = new SortedList<>(new Comparator<ProjectTemplatesGroup>() {
@Override
public int compare(ProjectTemplatesGroup o1, ProjectTemplatesGroup o2) {
//Put DSL/Development groups on top & Other group to the last place
if (o1.getName().equals("DSL")) {
return -1;
}
if (o2.getName().equals("DSL")) {
return 1;
}
if (o1.getName().equals("Development")) {
return -1;
}
if (o2.getName().equals("Development")) {
return 1;
}
if (o1.getName().equals("Other")) {
return 1;
}
if (o2.getName().equals("Other")) {
return -1;
}
return o1.getName().compareToIgnoreCase(o2.getName());
}
});
List<TemplateItem> templateItems = new LinkedList<>();
templatesGroups.addAll(Arrays.asList(ProjectTemplatesGroup.EP_NAME.getExtensions()));
for (ProjectTemplatesGroup templatesGroup : templatesGroups) {
for (MPSProjectTemplate template : templatesGroup.getTemplates()) {
templateItems.add(new TemplateItem(template, templatesGroup));
}
}
CollectionListModel<TemplateItem> model = new CollectionListModel<>(templateItems);
myFilteringListModel = new FilteringListModel<>(model);
myFilteringListModel.setFilter(templateItem -> templateItem.getName().toLowerCase().contains(mySearchField.getText().trim().toLowerCase()));
myList.setModel(myFilteringListModel);
myList.setCellRenderer(new GroupedItemsListRenderer(new ListItemDescriptor() {
@Nullable
@Override
public String getTextFor(Object value) {
return ((TemplateItem) value).getName();
}
@Nullable
@Override
public String getTooltipFor(Object value) {
return null;
}
@Nullable
@Override
public Icon getIconFor(Object value) {
return ((TemplateItem) value).getIcon();
}
@Override
public boolean hasSeparatorAboveOf(Object value) {
TemplateItem item = (TemplateItem) value;
int index = myFilteringListModel.getElementIndex(item);
return index == 0 || !myFilteringListModel.getElementAt(index - 1).getGroupName().equals(item.getGroupName());
}
@Nullable
@Override
public String getCaptionAboveOf(Object value) {
return ((TemplateItem) value).getGroupName();
}
}));
myList.getSelectionModel().addListSelectionListener(listSelectionEvent -> {
myCurrentTemplateItem = (TemplateItem) myList.getSelectedValue();
updateRightPanel();
});
myLeftPanel.add(ScrollPaneFactory.createScrollPane(myList),
new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK,
GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_CAN_SHRINK, null, null, null));
myLeftPanel.setPreferredSize(new Dimension(200, 400));
}
private void initRightPanel() {
myRightPanel = new JPanel(new GridLayoutManager(4, 1, new JBInsets(5, 5, 5, 0), -1, -1));
//-----Project panel-----
myProjectPanel = new JPanel(new GridLayoutManager(2, 2, new JBInsets(0, 0, 5, 0), -1, -1));
myProjectPanel.add(new JLabel("Project name:"),
new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED, null, null, null));
myProjectName = new JTextField(getDefaultProjectName());
myProjectPanel.add(myProjectName,
new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
null, null));
myProjectPanel.add(new JLabel("Project location:"),
new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED,
GridConstraints.SIZEPOLICY_FIXED, null, null, null));
myProjectPath = new PathField();
myProjectPath.addPathChangedListner(newPathValue -> {
//If path changed need to update specific module settings
fireProjectPathChanged(newPathValue);
checkProjectPath(newPathValue);
});
//Change project path if project name changed
myProjectName.addCaretListener(new CaretListener() {
private String myValue = myProjectName.getText();
@Override
public void caretUpdate(CaretEvent e) {
if (!Objects.equals(myValue, myProjectName.getText())) {
myValue = myProjectName.getText();
updateProjectPath();
}
}
});
updateProjectPath();
myProjectPanel.add(myProjectPath,
new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
null, null));
myRightPanel.add(myProjectPanel,
new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
null, null));
//-----Description panel-----
myDescriptionPanel = new JPanel(new GridLayoutManager(1, 1, JBUI.emptyInsets(), -1, -1));
myDescriptionPanel.setBorder(IdeBorderFactory.createTitledBorder("Description", true));
myDescriptionPane = new JTextPane();
myDescriptionPanel.add(myDescriptionPane, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null,
null));
myRightPanel.add(myDescriptionPanel,
new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null));
myRightPanel.add(Box.createVerticalGlue(),
new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null));
//-----Template settings panel-----
myTemplateSettingsHolder = new JPanel(new BorderLayout());
myTemplateSettings = new JPanel(new GridLayoutManager(2, 1, JBUI.emptyInsets(), -1, -1));
myTemplateSettings.setBorder(IdeBorderFactory.createEmptyBorder(0, IdeBorderFactory.TITLED_BORDER_INDENT, 5, 0));
myHideableDecorator = new HideableDecorator(myTemplateSettingsHolder, "More Settings", false);
myHideableDecorator.setContentComponent(myTemplateSettings);
myHideableDecorator.setOn(true);
myRightPanel.add(myTemplateSettingsHolder,
new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_SOUTHWEST, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null,
null, null));
}
private void checkProjectPath(String newProjectPath) {
boolean isProjectPath = false;
File file = new File(newProjectPath);
if (file.exists()) {
if (file.getParent() == null || !file.isDirectory()) {
isProjectPath = false;
} else if (file.isDirectory()) {
isProjectPath = file.listFiles(
(dir, name) -> Project.DIRECTORY_STORE_FOLDER.equals(name) || name.toLowerCase().endsWith(MPSExtentions.DOT_MPS_PROJECT)).length == 1;
}
}
if (isProjectPath) {
//show error and disable apply
setErrorText("Project under this path already exists!");
getOKAction().setEnabled(false);
} else {
setErrorText(null);
getOKAction().setEnabled(true);
}
}
private String getDefaultProjectName() {
int n = 1;
while (true) {
String projectName = "Project" + n;
if (!(new File(PROJECTS_DIR, projectName).exists())) {
return projectName;
}
n = n + 1;
}
}
private void updateProjectPath() {
if (myProjectPath.getPath() == null || myProjectPath.getPath().length() == 0 ||
(myProjectPath.getPath().startsWith(PROJECTS_DIR) && !myProjectPath.isPathChangedByUser())) {
myProjectPath.setPath(PROJECTS_DIR + File.separator + myProjectName.getText());
}
}
private void fireProjectPathChanged(String newValue) {
if (myCurrentTemplateItem != null) {
myCurrentTemplateItem.setNewProjectPath(newValue);
}
}
private void updateRightPanel() {
setOKActionEnabled(myCurrentTemplateItem != null);
checkProjectPath(myProjectPath.getPath());
String description = myCurrentTemplateItem != null ? myCurrentTemplateItem.getDescription() : null;
if (StringUtil.isNotEmpty(description)) {
UIUtil.setTextPaneHtmlText(myDescriptionPane, description);
myDescriptionPanel.setVisible(true);
} else {
myDescriptionPane.setText("");
myDescriptionPanel.setVisible(false);
}
JComponent component = myCurrentTemplateItem != null ? myCurrentTemplateItem.getSettings() : null;
myTemplateSettings.removeAll();
if (component != null) {
myTemplateSettings.add(component,
new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_BOTH,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null));
myTemplateSettings.add(myProjectFormatPanel.getPanel(),
new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED,
null, null, null, 1));
}
if (myCurrentTemplateItem != null) {
myCurrentTemplateItem.setNewProjectPath(myProjectPath.getPath());
}
myTemplateSettingsHolder.setVisible(component != null);
}
@Nullable
@Override
protected String getHelpId() {
return "MPS_New_Project_Page_1";
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myList;
}
@Override
protected void doOKAction() {
super.doOKAction();
if (myCurrentProject != null) {
int exitCode = Messages.showDialog(IdeBundle.message("prompt.open.project.in.new.frame"), IdeBundle.message("title.open.project"),
new String[]{IdeBundle.message("button.newframe"), IdeBundle.message("button.existingframe")}, 1,
Messages.getQuestionIcon());
if (exitCode == 1) {
ProjectUtil.closeAndDispose(myCurrentProject);
}
}
final ProjectOptions myOptions = new ProjectOptions();
myOptions.setProjectName(myProjectName.getText());
myOptions.setProjectPath(myProjectPath.getPath());
myOptions.setCreateNewLanguage(false);
myOptions.setCreateNewSolution(false);
myOptions.setStorageScheme(myProjectFormatPanel.isDefault());
//invoke later is for plugins to be ready
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
try {
ProjectFactory factory = new ProjectFactory(myOptions);
Project project = factory.createProject();
myCurrentTemplateItem.getTemplateFiller().fillProjectWithModules(project.getComponent(MPSProject.class));
factory.activate();
} catch (ProjectNotCreatedException e) {
Messages.showErrorDialog(getContentPane(), e.getMessage());
}
}
});
}
class TemplateItem {
private final MPSProjectTemplate myTemplate;
private final ProjectTemplatesGroup myGroup;
TemplateItem(MPSProjectTemplate template, ProjectTemplatesGroup group) {
myTemplate = template;
myGroup = group;
}
String getName() {
return myTemplate.getName();
}
String getGroupName() {
return myGroup.getName();
}
Icon getIcon() {
return myTemplate.getIcon();
}
@Nullable
String getDescription() {
return myTemplate.getDescription();
}
@Nullable
JComponent getSettings() {
return myTemplate.getSettings();
}
TemplateFiller getTemplateFiller() {
return myTemplate.getTemplateFiller();
}
void setNewProjectPath(String newValue) {
myTemplate.setProjectPath(newValue);
}
}
}