/* * Copyright 2000-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 com.intellij.ide.projectWizard; import com.intellij.CommonBundle; import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider; import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.frameworkSupport.FrameworkRole; import com.intellij.ide.util.frameworkSupport.FrameworkSupportUtil; import com.intellij.ide.util.newProjectWizard.AddSupportForFrameworksPanel; import com.intellij.ide.util.newProjectWizard.FrameworkSupportNode; import com.intellij.ide.util.newProjectWizard.TemplatesGroup; import com.intellij.ide.util.newProjectWizard.impl.FrameworkSupportModelBase; import com.intellij.ide.util.newProjectWizard.modes.CreateFromTemplateMode; import com.intellij.ide.util.projectWizard.*; import com.intellij.ide.wizard.CommitStepException; import com.intellij.openapi.Disposable; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.module.WebModuleTypeBase; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ui.configuration.ModulesProvider; import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContainer; import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContainerFactory; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.popup.ListItemDescriptor; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.platform.ProjectTemplate; import com.intellij.platform.ProjectTemplateEP; import com.intellij.platform.templates.*; import com.intellij.ui.CollectionListModel; import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.ListSpeedSearch; import com.intellij.ui.SingleSelectionModel; import com.intellij.ui.components.JBList; import com.intellij.ui.popup.list.GroupedItemsListRenderer; import com.intellij.util.Function; import com.intellij.util.PlatformUtils; import com.intellij.util.containers.*; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.net.URL; import java.util.*; import java.util.HashMap; import java.util.HashSet; import java.util.List; /** * @author Dmitry Avdeev * Date: 04.09.13 */ @SuppressWarnings("unchecked") public class ProjectTypeStep extends ModuleWizardStep implements SettingsStep, Disposable { private static final String TEMPLATES_CARD = "templates card"; private static final String FRAMEWORKS_CARD = "frameworks card"; private static final String PROJECT_WIZARD_GROUP = "project.wizard.group"; public static final Convertor PROVIDER_STRING_CONVERTOR = new Convertor() { @Override public String convert(FrameworkSupportInModuleProvider o) { return o.getId(); } }; public static final Function NODE_STRING_FUNCTION = new Function() { @Override public String fun(FrameworkSupportNode node) { return node.getId(); } }; private JPanel myPanel; private JPanel myOptionsPanel; private JBList myProjectTypeList; private ProjectTemplateList myTemplatesList; private JPanel myFrameworksPanelPlaceholder; private JPanel myHeaderPanel; private final WizardContext myContext; private final NewProjectWizard myWizard; private final ModulesProvider myModulesProvider; private final AddSupportForFrameworksPanel myFrameworksPanel; private final ModuleBuilder.ModuleConfigurationUpdater myConfigurationUpdater; @Nullable private ModuleWizardStep mySettingsStep; @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final FactoryMap myBuilders = new FactoryMap() { @Nullable @Override protected ModuleBuilder create(ProjectTemplate key) { return (ModuleBuilder)key.createModuleBuilder(); } }; private final Map myCustomSteps = new HashMap(); private final MultiMap myTemplatesMap; private String myCurrentCard; private TemplatesGroup myLastSelectedGroup; public ProjectTypeStep(WizardContext context, NewProjectWizard wizard, ModulesProvider modulesProvider) { myContext = context; myWizard = wizard; myTemplatesMap = new ConcurrentMultiMap(); final List groups = fillTemplatesMap(context); myProjectTypeList.setModel(new CollectionListModel(groups)); myProjectTypeList.setSelectionModel(new SingleSelectionModel()); myProjectTypeList.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { updateSelection(); } }); myProjectTypeList.setCellRenderer(new GroupedItemsListRenderer(new ListItemDescriptor() { @Nullable @Override public String getTextFor(TemplatesGroup value) { return value.getName(); } @Nullable @Override public String getTooltipFor(TemplatesGroup value) { return value.getDescription(); } @Nullable @Override public Icon getIconFor(TemplatesGroup value) { return value.getIcon(); } @Override public boolean hasSeparatorAboveOf(TemplatesGroup value) { int index = groups.indexOf(value); if (index < 1) return false; TemplatesGroup upper = groups.get(index - 1); if (upper.getParentGroup() == null && value.getParentGroup() == null) return true; return !Comparing.equal(upper.getParentGroup(), value.getParentGroup()) && !Comparing.equal(upper.getName(), value.getParentGroup()); } @Nullable @Override public String getCaptionAboveOf(TemplatesGroup value) { return null; } }) { @Override protected JComponent createItemComponent() { JComponent component = super.createItemComponent(); myTextLabel.setBorder(IdeBorderFactory.createEmptyBorder(3)); return component; } }); new ListSpeedSearch(myProjectTypeList) { @Override protected String getElementText(Object element) { return ((TemplatesGroup)element).getName(); } }; myModulesProvider = modulesProvider; Project project = context.getProject(); final LibrariesContainer container = LibrariesContainerFactory.createContainer(context, modulesProvider); FrameworkSupportModelBase model = new FrameworkSupportModelBase(project, null, container) { @NotNull @Override public String getBaseDirectoryForLibrariesPath() { ModuleBuilder builder = getSelectedBuilder(); return StringUtil.notNullize(builder.getContentEntryPath()); } @Override public ModuleBuilder getModuleBuilder() { return getSelectedBuilder(); } }; myFrameworksPanel = new AddSupportForFrameworksPanel(Collections.emptyList(), model, true, myHeaderPanel); Disposer.register(this, myFrameworksPanel); myFrameworksPanelPlaceholder.add(myFrameworksPanel.getMainPanel()); myConfigurationUpdater = new ModuleBuilder.ModuleConfigurationUpdater() { @Override public void update(@NotNull Module module, @NotNull ModifiableRootModel rootModel) { if (isFrameworksMode()) { myFrameworksPanel.addSupport(module, rootModel); } } }; myProjectTypeList.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { projectTypeChanged(); } }); myTemplatesList.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { updateSelection(); } }); for (TemplatesGroup templatesGroup : myTemplatesMap.keySet()) { ModuleBuilder builder = templatesGroup.getModuleBuilder(); if (builder != null) { myWizard.getSequence().addStepsForBuilder(builder, context, modulesProvider); } for (ProjectTemplate template : myTemplatesMap.get(templatesGroup)) { myWizard.getSequence().addStepsForBuilder(myBuilders.get(template), context, modulesProvider); } } final String groupId = PropertiesComponent.getInstance().getValue(PROJECT_WIZARD_GROUP); if (groupId != null) { TemplatesGroup group = ContainerUtil.find(groups, new Condition() { @Override public boolean value(TemplatesGroup group) { return groupId.equals(group.getId()); } }); if (group != null) { myProjectTypeList.setSelectedValue(group, true); } } if (myProjectTypeList.getSelectedValue() == null) { myProjectTypeList.setSelectedIndex(0); } myTemplatesList.restoreSelection(); } private boolean isFrameworksMode() { return FRAMEWORKS_CARD.equals(myCurrentCard) && getSelectedBuilder().equals(myContext.getProjectBuilder()); } private List fillTemplatesMap(WizardContext context) { List builders = ModuleBuilder.getAllBuilders(); if (context.isCreatingNewProject()) { builders.add(new EmptyModuleBuilder()); } Map groupMap = new HashMap(); for (ModuleBuilder builder : builders) { BuilderBasedTemplate template = new BuilderBasedTemplate(builder); if (builder.isTemplate()) { TemplatesGroup group = groupMap.get(builder.getGroupName()); if (group == null) { group = new TemplatesGroup(builder); } myTemplatesMap.putValue(group, template); } else { TemplatesGroup group = new TemplatesGroup(builder); groupMap.put(group.getName(), group); myTemplatesMap.put(group, new ArrayList()); } } MultiMap map = CreateFromTemplateMode.getTemplatesMap(context); myTemplatesMap.putAllValues(map); for (ProjectCategory category : ProjectCategory.EXTENSION_POINT_NAME.getExtensions()) { TemplatesGroup group = new TemplatesGroup(category); myTemplatesMap.remove(group); myTemplatesMap.put(group, new ArrayList()); } if (context.isCreatingNewProject()) { MultiMap localTemplates = loadLocalTemplates(); for (TemplatesGroup group : myTemplatesMap.keySet()) { myTemplatesMap.putValues(group, localTemplates.get(group.getId())); } } // remove Static Web group in IDEA Community if no specific templates found (IDEA-120593) if (PlatformUtils.isIdeaCommunity()) { for (TemplatesGroup group : myTemplatesMap.keySet()) { if (WebModuleTypeBase.WEB_MODULE.equals(group.getId()) && myTemplatesMap.get(group).isEmpty()) { myTemplatesMap.remove(group); break; } } } List groups = new ArrayList(myTemplatesMap.keySet()); // sorting by module type popularity final MultiMap moduleTypes = new MultiMap(); for (TemplatesGroup group : groups) { ModuleType type = getModuleType(group); moduleTypes.putValue(type, group); } Collections.sort(groups, new Comparator() { @Override public int compare(TemplatesGroup o1, TemplatesGroup o2) { int i = o2.getWeight() - o1.getWeight(); if (i != 0) return i; int i1 = moduleTypes.get(getModuleType(o2)).size() - moduleTypes.get(getModuleType(o1)).size(); if (i1 != 0) return i1; return o1.compareTo(o2); } }); Set groupNames = ContainerUtil.map2Set(groups, new Function() { @Override public String fun(TemplatesGroup group) { return group.getParentGroup(); } }); // move subgroups MultiMap subGroups = new MultiMap(); for (ListIterator iterator = groups.listIterator(); iterator.hasNext(); ) { TemplatesGroup group = iterator.next(); String parentGroup = group.getParentGroup(); if (parentGroup != null && groupNames.contains(parentGroup) && !group.getName().equals(parentGroup) && groupMap.containsKey(parentGroup)) { subGroups.putValue(parentGroup, group); iterator.remove(); } } for (ListIterator iterator = groups.listIterator(); iterator.hasNext(); ) { TemplatesGroup group = iterator.next(); for (TemplatesGroup subGroup : subGroups.get(group.getName())) { iterator.add(subGroup); } } return groups; } private static ModuleType getModuleType(TemplatesGroup group) { ModuleBuilder moduleBuilder = group.getModuleBuilder(); return moduleBuilder == null ? null : moduleBuilder.getModuleType(); } // new TemplatesGroup selected public void projectTypeChanged() { TemplatesGroup group = getSelectedGroup(); if (group == null || group == myLastSelectedGroup) return; myLastSelectedGroup = group; PropertiesComponent.getInstance().setValue(PROJECT_WIZARD_GROUP, group.getId() ); ModuleBuilder groupModuleBuilder = group.getModuleBuilder(); mySettingsStep = null; myHeaderPanel.removeAll(); if (groupModuleBuilder != null && groupModuleBuilder.getModuleType() != null) { mySettingsStep = groupModuleBuilder.modifyProjectTypeStep(this); } if (groupModuleBuilder == null || groupModuleBuilder.isTemplateBased()) { showTemplates(group); } else if (!showCustomOptions(groupModuleBuilder)){ List providers = FrameworkSupportUtil.getProviders(groupModuleBuilder); final ProjectCategory category = group.getProjectCategory(); if (category != null) { List filtered = ContainerUtil.filter(providers, new Condition() { @Override public boolean value(FrameworkSupportInModuleProvider provider) { return matchFramework(category, provider); } }); // add associated Map map = ContainerUtil.newMapFromValues(providers.iterator(), PROVIDER_STRING_CONVERTOR); Set set = new HashSet(filtered); for (FrameworkSupportInModuleProvider provider : filtered) { for (FrameworkSupportInModuleProvider.FrameworkDependency depId : provider.getDependenciesFrameworkIds()) { FrameworkSupportInModuleProvider dependency = map.get(depId.getFrameworkId()); set.add(dependency); } } myFrameworksPanel.setProviders(new ArrayList(set), new HashSet(Arrays.asList(category.getAssociatedFrameworkIds())), new HashSet(Arrays.asList(category.getPreselectedFrameworkIds()))); } else { myFrameworksPanel.setProviders(providers); } getSelectedBuilder().addModuleConfigurationUpdater(myConfigurationUpdater); showCard(FRAMEWORKS_CARD); } myHeaderPanel.setVisible(myHeaderPanel.getComponentCount() > 0); // align header labels List labels = UIUtil.findComponentsOfType(myHeaderPanel, JLabel.class); int width = 0; for (JLabel label : labels) { int width1 = label.getPreferredSize().width; width = Math.max(width, width1); } for (JLabel label : labels) { label.setPreferredSize(new Dimension(width, label.getPreferredSize().height)); } myHeaderPanel.revalidate(); myHeaderPanel.repaint(); updateSelection(); } private void showCard(String card) { ((CardLayout)myOptionsPanel.getLayout()).show(myOptionsPanel, card); myCurrentCard = card; } private void showTemplates(TemplatesGroup group) { Collection templates = myTemplatesMap.get(group); setTemplatesList(group, templates, false); showCard(TEMPLATES_CARD); } private static boolean matchFramework(ProjectCategory projectCategory, FrameworkSupportInModuleProvider framework) { FrameworkRole[] roles = framework.getRoles(); if (roles.length == 0) return true; List acceptable = Arrays.asList(projectCategory.getAcceptableFrameworkRoles()); return ContainerUtil.intersects(Arrays.asList(roles), acceptable); } private void setTemplatesList(TemplatesGroup group, Collection templates, boolean preserveSelection) { List list = new ArrayList(templates); ModuleBuilder moduleBuilder = group.getModuleBuilder(); if (moduleBuilder != null && !(moduleBuilder instanceof TemplateModuleBuilder)) { list.add(0, new BuilderBasedTemplate(moduleBuilder)); } myTemplatesList.setTemplates(list, preserveSelection); } private boolean showCustomOptions(@NotNull ModuleBuilder builder) { String card = builder.getBuilderId(); if (!myCustomSteps.containsKey(card)) { ModuleWizardStep step = builder.getCustomOptionsStep(myContext, this); if (step == null) return false; step.updateStep(); myCustomSteps.put(card, step); myOptionsPanel.add(step.getComponent(), card); } showCard(card); return true; } @Nullable private ModuleWizardStep getCustomStep() { return myCustomSteps.get(myCurrentCard); } private TemplatesGroup getSelectedGroup() { return (TemplatesGroup)myProjectTypeList.getSelectedValue(); } @Nullable public ProjectTemplate getSelectedTemplate() { return myCurrentCard == TEMPLATES_CARD ? myTemplatesList.getSelectedTemplate() : null; } private ModuleBuilder getSelectedBuilder() { ProjectTemplate template = getSelectedTemplate(); if (template != null) { return myBuilders.get(template); } return getSelectedGroup().getModuleBuilder(); } public Collection getAvailableTemplates() { if (myCurrentCard != FRAMEWORKS_CARD) { return Collections.emptyList(); } else { Collection templates = myTemplatesMap.get(getSelectedGroup()); List nodes = myFrameworksPanel.getSelectedNodes(); if (nodes.isEmpty()) return templates; final List selectedFrameworks = ContainerUtil.map(nodes, NODE_STRING_FUNCTION); return ContainerUtil.filter(templates, new Condition() { @Override public boolean value(ProjectTemplate template) { if (!(template instanceof ArchivedProjectTemplate)) return true; List frameworks = ((ArchivedProjectTemplate)template).getFrameworks(); return frameworks.containsAll(selectedFrameworks); } }); } } public void onWizardFinished() throws CommitStepException { if (isFrameworksMode()) { boolean ok = myFrameworksPanel.downloadLibraries(); if (!ok) { int answer = Messages.showYesNoDialog(getComponent(), ProjectBundle.message("warning.message.some.required.libraries.wasn.t.downloaded"), CommonBundle.getWarningTitle(), Messages.getWarningIcon()); if (answer != Messages.YES) { throw new CommitStepException(null); } } } } @Override public JComponent getComponent() { return myPanel; } @Override public void updateDataModel() { ModuleBuilder builder = getSelectedBuilder(); myWizard.getSequence().addStepsForBuilder(builder, myContext, myModulesProvider); ModuleWizardStep step = getCustomStep(); if (step != null) { step.updateDataModel(); } if (mySettingsStep != null) { mySettingsStep.updateDataModel(); } } @Override public boolean validate() throws ConfigurationException { if (mySettingsStep != null) { if (!mySettingsStep.validate()) return false; } ModuleWizardStep step = getCustomStep(); return step != null ? step.validate() : super.validate(); } @Override public JComponent getPreferredFocusedComponent() { return myProjectTypeList; } @Override public void dispose() { } @Override public void disposeUIResources() { Disposer.dispose(this); } private MultiMap loadLocalTemplates() { ConcurrentMultiMap map = new ConcurrentMultiMap(); ProjectTemplateEP[] extensions = ProjectTemplateEP.EP_NAME.getExtensions(); for (ProjectTemplateEP ep : extensions) { ClassLoader classLoader = ep.getLoaderForClass(); URL url = classLoader.getResource(ep.templatePath); if (url != null) { LocalArchivedTemplate template = new LocalArchivedTemplate(url, classLoader); if (ep.category) { TemplateBasedCategory category = new TemplateBasedCategory(template, ep.projectType); myTemplatesMap.putValue(new TemplatesGroup(category), template); } else { map.putValue(ep.projectType, template); } } } return map; } void loadRemoteTemplates(final ChooseTemplateStep chooseTemplateStep) { ProgressManager.getInstance().run(new Task.Backgroundable(myContext.getProject(), "Loading Templates") { @Override public void run(@NotNull ProgressIndicator indicator) { try { myTemplatesList.setPaintBusy(true); chooseTemplateStep.getTemplateList().setPaintBusy(true); RemoteTemplatesFactory factory = new RemoteTemplatesFactory(); String[] groups = factory.getGroups(); for (String group : groups) { ProjectTemplate[] templates = factory.createTemplates(group, myContext); for (ProjectTemplate template : templates) { String id = ((ArchivedProjectTemplate)template).getCategory(); for (TemplatesGroup templatesGroup : myTemplatesMap.keySet()) { if (Comparing.equal(id, templatesGroup.getId()) || Comparing.equal(group, templatesGroup.getName())) { myTemplatesMap.putValue(templatesGroup, template); } } } } //noinspection SSBasedInspection SwingUtilities.invokeLater(new Runnable() { public void run() { TemplatesGroup group = getSelectedGroup(); if (group == null) return; Collection templates = myTemplatesMap.get(group); setTemplatesList(group, templates, true); chooseTemplateStep.updateStep(); } }); } finally { myTemplatesList.setPaintBusy(false); chooseTemplateStep.getTemplateList().setPaintBusy(false); } } }); } private void updateSelection() { ProjectTemplate template = getSelectedTemplate(); if (template != null) { myContext.setProjectTemplate(template); } ModuleBuilder builder = getSelectedBuilder(); myContext.setProjectBuilder(builder); if (builder != null) { myWizard.getSequence().setType(builder.getBuilderId()); } } @TestOnly public boolean setSelectedTemplate(String group, String name) { ListModel model = myProjectTypeList.getModel(); for (int i = 0; i < model.getSize(); i++) { TemplatesGroup templatesGroup = (TemplatesGroup)model.getElementAt(i); if (group.equals(templatesGroup.getName())) { myProjectTypeList.setSelectedIndex(i); if (name == null) return getSelectedGroup().getName().equals(group); Collection templates = myTemplatesMap.get(templatesGroup); setTemplatesList(templatesGroup, templates, false); return myTemplatesList.setSelectedTemplate(name); } } return false; } @TestOnly public AddSupportForFrameworksPanel getFrameworksPanel() { return myFrameworksPanel; } @Override public WizardContext getContext() { return myContext; } @Override public void addSettingsField(@NotNull String label, @NotNull JComponent field) { ProjectSettingsStep.addField(label, field, myHeaderPanel); } @Override public void addSettingsComponent(@NotNull JComponent component) { } @Override public void addExpertPanel(@NotNull JComponent panel) { } @Override public void addExpertField(@NotNull String label, @NotNull JComponent field) { } @Override public JTextField getModuleNameField() { return null; } }