diff --git a/platform/lang-impl/src/com/intellij/ide/util/projectWizard/WebProjectTemplate.java b/platform/lang-impl/src/com/intellij/ide/util/projectWizard/WebProjectTemplate.java index bade6b79ae23..9373c7b28c91 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/projectWizard/WebProjectTemplate.java +++ b/platform/lang-impl/src/com/intellij/ide/util/projectWizard/WebProjectTemplate.java @@ -15,11 +15,9 @@ */ package com.intellij.ide.util.projectWizard; -import com.intellij.openapi.module.*; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ui.configuration.ModulesProvider; +import com.intellij.openapi.module.WebModuleBuilder; +import com.intellij.openapi.module.WebModuleType; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.NotNullLazyValue; import com.intellij.platform.ProjectTemplate; @@ -28,7 +26,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.List; +import java.io.File; /** * @author Dmitry Avdeev @@ -64,4 +62,16 @@ public abstract class WebProjectTemplate extends WebProjectGenerator imple public GeneratorPeer getPeer() { return myPeerHolder.getValue(); } + + @Override + @Nullable + public JComponent getSettingsPanel(File baseDir) throws ProcessCanceledException { + return getPeer().getComponent(); + } + + @Override + public T getProjectSettings() throws ProcessCanceledException { + return getPeer().getSettings(); + } + } diff --git a/platform/platform-impl/src/com/intellij/platform/DirectoryProjectGenerator.java b/platform/platform-impl/src/com/intellij/platform/DirectoryProjectGenerator.java index c99bf6afe372..fe98588d6b63 100644 --- a/platform/platform-impl/src/com/intellij/platform/DirectoryProjectGenerator.java +++ b/platform/platform-impl/src/com/intellij/platform/DirectoryProjectGenerator.java @@ -25,6 +25,9 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; +import java.io.File; + /** * @author yole */ @@ -38,6 +41,15 @@ public interface DirectoryProjectGenerator { @Nullable T showGenerationSettings(final VirtualFile baseDir) throws ProcessCanceledException; + @Nullable + JComponent getSettingsPanel(final File baseDir) throws ProcessCanceledException; + + @Nullable + T getProjectSettings() throws ProcessCanceledException; + + @Nullable + Icon getIcon(); + void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir, @Nullable final T settings, @NotNull final Module module); diff --git a/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectAction.java b/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectAction.java new file mode 100644 index 000000000000..85f649c6f6cf --- /dev/null +++ b/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectAction.java @@ -0,0 +1,27 @@ +/* + * 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.jetbrains.python.newProject; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; + +public class PyCharmNewProjectAction extends AnAction { + + public void actionPerformed(final AnActionEvent e) { + final PyCharmNewProjectDialog dlg = new PyCharmNewProjectDialog(); + dlg.show(); + } +} diff --git a/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectDialog.java b/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectDialog.java new file mode 100644 index 000000000000..61e7ecb0fdc9 --- /dev/null +++ b/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectDialog.java @@ -0,0 +1,70 @@ +/* + * 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.jetbrains.python.newProject; + +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.wm.impl.welcomeScreen.CardActionsPanel; +import com.jetbrains.python.newProject.actions.PyCharmNewProjectStep; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +public class PyCharmNewProjectDialog extends DialogWrapper { + public PyCharmNewProjectDialog() { + super(ProjectManager.getInstance().getDefaultProject()); + setTitle(null); + init(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + final Runnable runnable = new Runnable() { + @Override + public void run() { + PyCharmNewProjectDialog.this.close(OK_EXIT_CODE); + } + }; + final DefaultActionGroup root = new PyCharmNewProjectStep(runnable); + + return new CardActionsPanel(root) { + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + @Override + public Dimension getMinimumSize() { + return new Dimension(800, 500); + } + }; + } + + @Override + protected String getHelpId() { + return null; + } + + @NotNull + protected Action[] createActions() { + return new Action[0]; + } +} diff --git a/python/ide/src/com/jetbrains/python/newProject/PythonBaseProjectGenerator.java b/python/ide/src/com/jetbrains/python/newProject/PythonBaseProjectGenerator.java new file mode 100644 index 000000000000..0debab268799 --- /dev/null +++ b/python/ide/src/com/jetbrains/python/newProject/PythonBaseProjectGenerator.java @@ -0,0 +1,79 @@ +package com.jetbrains.python.newProject; + +import com.intellij.facet.ui.ValidationResult; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootModificationUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.platform.DirectoryProjectGenerator; +import icons.PythonIcons; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.File; + +public class PythonBaseProjectGenerator implements DirectoryProjectGenerator { + @NotNull + @Nls + @Override + public String getName() { + return "Basic project"; + } + + @Nullable + @Override + public Object showGenerationSettings(VirtualFile baseDir) throws ProcessCanceledException { + return null; + } + + @Override + @Nullable + public JComponent getSettingsPanel(File baseDir) throws ProcessCanceledException { + return null; + } + + @Override + public Object getProjectSettings() throws ProcessCanceledException { + return new PyNewProjectSettings(); + } + + @Nullable + @Override + public Icon getIcon() { + return PythonIcons.Python.Python_logo; + } + + @Override + public void generateProject(@NotNull final Project project, @NotNull VirtualFile baseDir, final Object settings, + @NotNull final Module module) { + /*if (settings instanceof RemoteProjectSettings) { + PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); + assert manager != null; + manager.createDeployment(project, baseDir, (RemoteProjectSettings)settings, + (RemoteSdkCredentials)myProjectAction.getSdk().getSdkAdditionalData()); + } + else */if (settings instanceof PyNewProjectSettings) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + ModuleRootModificationUtil.setModuleSdk(module, ((PyNewProjectSettings)settings).getSdk()); + } + }); + } + } + + @NotNull + @Override + public ValidationResult validate(@NotNull String baseDirPath) { + /*if (PythonSdkType.isRemote(myProjectAction.getSdk())) { + if (PythonRemoteInterpreterManager.getInstance() == null) { + return new ValidationResult(PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED); + } + }*/ + return ValidationResult.OK; + } +} diff --git a/python/ide/src/com/jetbrains/python/newProject/PythonNewDirectoryProjectDialog.java b/python/ide/src/com/jetbrains/python/newProject/PythonNewDirectoryProjectDialog.java index fe497d4ce320..098fbaee246a 100644 --- a/python/ide/src/com/jetbrains/python/newProject/PythonNewDirectoryProjectDialog.java +++ b/python/ide/src/com/jetbrains/python/newProject/PythonNewDirectoryProjectDialog.java @@ -54,6 +54,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.File; import java.util.List; /** @@ -179,6 +180,24 @@ public class PythonNewDirectoryProjectDialog extends NewDirectoryProjectDialog { } } + @Nullable + @Override + public JComponent getSettingsPanel(File baseDir) throws ProcessCanceledException { + return null; + } + + @Nullable + @Override + public Object getProjectSettings() throws ProcessCanceledException { + return null; + } + + @Nullable + @Override + public Icon getIcon() { + return null; + } + @Override public void generateProject(@NotNull final Project project, @NotNull VirtualFile baseDir, @@ -283,7 +302,7 @@ public class PythonNewDirectoryProjectDialog extends NewDirectoryProjectDialog { private boolean isFrameworkInstalled(Sdk sdk) { PyFrameworkProjectGenerator projectGenerator = (PyFrameworkProjectGenerator)getProjectGenerator(); - return projectGenerator != null && projectGenerator.isFrameworkInstalled(myProject, sdk); + return projectGenerator != null && projectGenerator.isFrameworkInstalled(sdk); } private static boolean acceptsRemoteSdk(DirectoryProjectGenerator generator) { diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java b/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java new file mode 100644 index 000000000000..15612fd00d5e --- /dev/null +++ b/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java @@ -0,0 +1,401 @@ +package com.jetbrains.python.newProject.actions; + +import com.intellij.facet.ui.ValidationResult; +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.ide.util.projectWizard.WebProjectTemplate; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.actionSystem.impl.ActionButtonWithText; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.wm.impl.welcomeScreen.AbstractActionWithPanel; +import com.intellij.platform.DirectoryProjectGenerator; +import com.intellij.platform.WebProjectGenerator; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.JBColor; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.util.NullableConsumer; +import com.intellij.util.ui.CenteredIcon; +import com.jetbrains.python.PythonSdkChooserCombo; +import com.jetbrains.python.configuration.PyConfigurableInterpreterList; +import com.jetbrains.python.configuration.VirtualEnvProjectFilter; +import com.jetbrains.python.newProject.PyFrameworkProjectGenerator; +import com.jetbrains.python.packaging.PyExternalProcessException; +import com.jetbrains.python.packaging.PyPackage; +import com.jetbrains.python.packaging.PyPackageManager; +import com.jetbrains.python.packaging.PyPackageManagerImpl; +import com.jetbrains.python.sdk.PythonSdkType; +import com.jetbrains.python.sdk.flavors.JythonSdkFlavor; +import com.jetbrains.python.sdk.flavors.PyPySdkFlavor; +import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; +import icons.PythonIcons; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.List; + +abstract public class AbstractProjectSettingsStep extends AbstractActionWithPanel implements DumbAware { + protected final DirectoryProjectGenerator myProjectGenerator; + private final NullableConsumer myCallback; + private PythonSdkChooserCombo mySdkCombo; + private JCheckBox myFrameworkCheckbox; + private boolean myInstallFrameworkChanged; + private TextFieldWithBrowseButton myLocationField; + protected final File myProjectDirectory; + private Button myCreateButton; + private JLabel myErrorLabel; + private AnAction myCreateAction; + + public AbstractProjectSettingsStep(DirectoryProjectGenerator projectGenerator, NullableConsumer callback) { + super(); + myProjectGenerator = projectGenerator; + myCallback = callback; + myProjectDirectory = FileUtil.findSequentNonexistentFile(new File(ProjectUtil.getBaseDir()), "untitled", ""); + if (myProjectGenerator instanceof WebProjectTemplate) { + ((WebProjectTemplate)myProjectGenerator).getPeer().addSettingsStateListener(new WebProjectGenerator.SettingsStateListener() { + @Override + public void stateChanged(boolean validSettings) { + if (validSettings) { + setErrorText(null); + } + } + }); + } + myCreateAction = new AnAction("Create ", "Create Project", getIcon()) { + @Override + public void actionPerformed(AnActionEvent e) { + boolean isOk; + try { + isOk = checkValid(); + if (myProjectGenerator instanceof WebProjectTemplate) { + final ValidationInfo validationInfo = ((WebProjectTemplate)myProjectGenerator).getPeer().validate(); + isOk = validationInfo == null; + if (!isOk) { + setErrorText(validationInfo.message); + } + } + } + catch (RuntimeException e1) { + isOk = false; + setErrorText(e1.getMessage()); + } + + if (isOk && myCallback != null) + myCallback.consume(AbstractProjectSettingsStep.this); + } + }; + } + + @Override + public void actionPerformed(AnActionEvent e) { + } + + @Override + public JPanel createPanel() { + final JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.setPreferredSize(new Dimension(mainPanel.getPreferredSize().width, 450)); + + final JPanel panel = createBasePanel(); + mainPanel.add(panel, BorderLayout.NORTH); + + final JPanel advancedSettings = createAdvancedSettings(); + if (advancedSettings != null) + mainPanel.add(advancedSettings, BorderLayout.CENTER); + + final JPanel bottomPanel = new JPanel(new BorderLayout()); + myCreateButton = new Button(myCreateAction, myCreateAction.getTemplatePresentation()); + + bottomPanel.setPreferredSize(new Dimension(mainPanel.getPreferredSize().width, 40)); + myCreateButton.setPreferredSize(new Dimension(mainPanel.getPreferredSize().width, 40)); + myErrorLabel = new JLabel(""); + myErrorLabel.setForeground(JBColor.RED); + bottomPanel.add(myErrorLabel, BorderLayout.WEST); + bottomPanel.add(myCreateButton, BorderLayout.CENTER); + mainPanel.add(bottomPanel, BorderLayout.SOUTH); + + return mainPanel; + } + + protected Icon getIcon() { + return myProjectGenerator.getIcon(); + } + + private JPanel createBasePanel() { + final JPanel panel = new JPanel(new GridBagLayout()); + final GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.NORTHWEST; + c.weightx = 0; + c.insets = new Insets(2, 2, 2, 2); + myLocationField = new TextFieldWithBrowseButton(); + myLocationField.setText(myProjectDirectory.toString()); + + final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + myLocationField.addBrowseFolderListener("Select base directory", "Select base directory for the Project", + null, descriptor); + + final JLabel locationLabel = new JLabel("Location:"); + c.gridx = 0; + c.gridy = 0; + panel.add(locationLabel, c); + + c.gridx = 1; + c.gridy = 0; + c.weightx = 1.; + panel.add(myLocationField, c); + + final JLabel interpreterLabel = new JLabel("Interpreter:", SwingConstants.LEFT) { + @Override + public Dimension getMinimumSize() { + return new JLabel("Project name:").getPreferredSize(); + } + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + }; + c.gridx = 0; + c.gridy = 1; + c.weightx = 0; + panel.add(interpreterLabel, c); + + final Project project = ProjectManager.getInstance().getDefaultProject(); + final List sdks = PyConfigurableInterpreterList.getInstance(project).getAllPythonSdks(); + VirtualEnvProjectFilter.removeAllAssociated(sdks); + final Sdk preferred = sdks.isEmpty() ? null : sdks.iterator().next(); + mySdkCombo = new PythonSdkChooserCombo(project, sdks, new Condition() { + @Override + public boolean value(Sdk sdk) { + return sdk == preferred; + } + }); + mySdkCombo.setButtonIcon(PythonIcons.Python.InterpreterGear); + + c.gridx = 1; + c.gridy = 1; + c.weightx = 1.; + panel.add(mySdkCombo, c); + + myFrameworkCheckbox = new JBCheckBox("Install "); + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 2; + c.weightx = 0.0; + panel.add(myFrameworkCheckbox, c); + myFrameworkCheckbox.setVisible(false); + + registerValidators(); + return panel; + } + + protected void registerValidators() { + myFrameworkCheckbox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myInstallFrameworkChanged = true; + checkValid(); + } + }); + + myLocationField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + checkValid(); + } + }); + final ActionListener listener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + checkValid(); + } + }; + mySdkCombo.getComboBox().addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + checkValid(); + } + }); + myLocationField.getTextField().addActionListener(listener); + mySdkCombo.getComboBox().addActionListener(listener); + mySdkCombo.addActionListener(listener); + } + + public boolean checkValid() { + final String projectName = myLocationField.getText(); + if (projectName.trim().isEmpty()) { + setErrorText("Project name can't be empty"); + return false; + } + if (myLocationField.getText().indexOf('$') >= 0) { + setErrorText("Project directory name must not contain the $ character"); + return false; + } + if (myProjectGenerator != null) { + final String baseDirPath = myLocationField.getTextField().getText(); + ValidationResult validationResult = myProjectGenerator.validate(baseDirPath); + if (!validationResult.isOk()) { + setErrorText(validationResult.getErrorMessage()); + return false; + } + + if (myProjectGenerator instanceof WebProjectTemplate) { + final ValidationInfo validationInfo = ((WebProjectTemplate)myProjectGenerator).getPeer().validate(); + if (validationInfo != null) { + setErrorText(validationInfo.message); + return false; + } + } + } + + final Sdk sdk = getSdk(); + setErrorText(null); + myFrameworkCheckbox.setVisible(false); + + final boolean isPy3k = sdk != null && PythonSdkType.getLanguageLevelForSdk(sdk).isPy3K(); + if (sdk != null && PythonSdkType.isRemote(sdk) && !acceptsRemoteSdk(myProjectGenerator)) { + setErrorText("Please choose a local interpreter"); + return false; + } + else if (myProjectGenerator instanceof PyFrameworkProjectGenerator) { + PyFrameworkProjectGenerator frameworkProjectGenerator = (PyFrameworkProjectGenerator)myProjectGenerator; + String frameworkName = frameworkProjectGenerator.getFrameworkTitle(); + if (sdk != null && !isFrameworkInstalled(sdk)) { + final PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk); + final boolean onlyWithCache = + PythonSdkFlavor.getFlavor(sdk) instanceof JythonSdkFlavor || PythonSdkFlavor.getFlavor(sdk) instanceof PyPySdkFlavor; + try { + if (onlyWithCache && packageManager.cacheIsNotNull() || !onlyWithCache) { + final PyPackage pip = packageManager.findPackage("pip"); + myFrameworkCheckbox.setText("Install " + frameworkName); + myFrameworkCheckbox.setMnemonic(frameworkName.charAt(0)); + myFrameworkCheckbox.setVisible(pip != null); + if (!myInstallFrameworkChanged) { + myFrameworkCheckbox.setSelected(pip != null); + } + } + } + catch (PyExternalProcessException e) { + myFrameworkCheckbox.setVisible(false); + } + if (!myFrameworkCheckbox.isSelected()) { + setErrorText("No " + frameworkName + " support installed in selected interpreter"); + return false; + } + } + if (isPy3k && !((PyFrameworkProjectGenerator)myProjectGenerator).supportsPython3()) { + setErrorText(frameworkName + " is not supported for the selected interpreter"); + return false; + } + } + if (sdk == null) { + setErrorText("No Python interpreter selected"); + return false; + } + return true; + } + + public void setErrorText(@Nullable String text) { + myErrorLabel.setText(text); + myCreateButton.setEnabled(text == null); + } + + public void selectCompatiblePython() { + DirectoryProjectGenerator generator = getProjectGenerator(); + if (generator instanceof PyFrameworkProjectGenerator && !((PyFrameworkProjectGenerator)generator).supportsPython3()) { + Sdk sdk = getSdk(); + if (sdk != null && PythonSdkType.getLanguageLevelForSdk(sdk).isPy3K()) { + Sdk python2Sdk = PythonSdkType.findPython2Sdk(null); + if (python2Sdk != null) { + mySdkCombo.getComboBox().setSelectedItem(python2Sdk); + mySdkCombo.getComboBox().repaint(); + } + } + } + } + + private static boolean acceptsRemoteSdk(DirectoryProjectGenerator generator) { + if (generator instanceof PyFrameworkProjectGenerator) { + return ((PyFrameworkProjectGenerator)generator).acceptsRemoteSdk(); + } + return true; + } + + private boolean isFrameworkInstalled(Sdk sdk) { + PyFrameworkProjectGenerator projectGenerator = (PyFrameworkProjectGenerator)getProjectGenerator(); + return projectGenerator != null && projectGenerator.isFrameworkInstalled(sdk); + } + + @Nullable + protected JPanel createAdvancedSettings() { + return null; + } + + public DirectoryProjectGenerator getProjectGenerator() { + return myProjectGenerator; + } + + private static class Button extends ActionButtonWithText { + private static final Icon DEFAULT_ICON = PythonIcons.Python.Python; + + public Button(AnAction action, Presentation presentation) { + super(action, + wrapIcon(presentation), + "NewProject", + new Dimension(32, 32)); + setBorder(new EmptyBorder(3, 3, 3, 3)); + } + + @Override + public String getToolTipText() { + return null; + } + + @Override + protected int horizontalTextAlignment() { + return SwingConstants.RIGHT; + } + + @Override + protected int iconTextSpace() { + return 8; + } + + private static Presentation wrapIcon(Presentation presentation) { + Icon original = presentation.getIcon(); + CenteredIcon centered = new CenteredIcon(original != null ? original : DEFAULT_ICON, 40, 40, false); + presentation.setIcon(centered); + return presentation; + } + } + + public Sdk getSdk() { + return (Sdk)mySdkCombo.getComboBox().getSelectedItem(); + } + + public String getProjectLocation() { + return myLocationField.getText(); + } + + public boolean installFramework() { + return myFrameworkCheckbox.isSelected() && myFrameworkCheckbox.isVisible(); + } + +} diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificAction.java b/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificAction.java new file mode 100644 index 000000000000..a2fa66dc4834 --- /dev/null +++ b/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificAction.java @@ -0,0 +1,47 @@ +/* + * 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.jetbrains.python.newProject.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.platform.DirectoryProjectGenerator; +import com.intellij.util.NullableConsumer; +import org.jetbrains.annotations.NotNull; + +public class ProjectSpecificAction extends DefaultActionGroup implements DumbAware { + + private final ProjectSpecificSettingsStep mySettings; + + public ProjectSpecificAction(@NotNull final NullableConsumer callback, + @NotNull final DirectoryProjectGenerator projectGenerator) { + super(projectGenerator.getName(), true); + getTemplatePresentation().setIcon(projectGenerator.getIcon()); + mySettings = new ProjectSpecificSettingsStep(projectGenerator, callback); + add(mySettings); + } + + public Sdk getSdk() { + return mySettings.getSdk(); + } + + @Override + public void actionPerformed(AnActionEvent e) { + super.actionPerformed(e); + mySettings.selectCompatiblePython(); + } +} diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificSettingsStep.java b/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificSettingsStep.java new file mode 100644 index 000000000000..2bbbe9e6ac2f --- /dev/null +++ b/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificSettingsStep.java @@ -0,0 +1,47 @@ +/* + * 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.jetbrains.python.newProject.actions; + +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.ui.VerticalFlowLayout; +import com.intellij.platform.DirectoryProjectGenerator; +import com.intellij.ui.HideableDecorator; +import com.intellij.util.NullableConsumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class ProjectSpecificSettingsStep extends AbstractProjectSettingsStep implements DumbAware { + + public ProjectSpecificSettingsStep(@NotNull final DirectoryProjectGenerator projectGenerator, + @NotNull final NullableConsumer callback) { + super(projectGenerator, callback); + } + + @Override + @Nullable + protected JPanel createAdvancedSettings() { + final JComponent advancedSettings = myProjectGenerator.getSettingsPanel(myProjectDirectory); + if (advancedSettings != null) { + final JPanel jPanel = new JPanel(new VerticalFlowLayout()); + final HideableDecorator deco = new HideableDecorator(jPanel, "Mor&e Settings", false); + deco.setContentComponent(advancedSettings); + return jPanel; + } + return null; + } +} diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/PyCharmNewProjectStep.java b/python/ide/src/com/jetbrains/python/newProject/actions/PyCharmNewProjectStep.java new file mode 100644 index 000000000000..4b37439f82ad --- /dev/null +++ b/python/ide/src/com/jetbrains/python/newProject/actions/PyCharmNewProjectStep.java @@ -0,0 +1,178 @@ +/* + * 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.jetbrains.python.newProject.actions; + +import com.intellij.ide.GeneralSettings; +import com.intellij.internal.statistic.UsageTrigger; +import com.intellij.internal.statistic.beans.ConvertUsagesUtil; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SdkAdditionalData; +import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil; +import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.platform.DirectoryProjectGenerator; +import com.intellij.platform.PlatformProjectOpenProcessor; +import com.intellij.projectImport.ProjectOpenedCallback; +import com.intellij.util.NullableConsumer; +import com.jetbrains.python.configuration.PyConfigurableInterpreterList; +import com.jetbrains.python.newProject.PyFrameworkProjectGenerator; +import com.jetbrains.python.newProject.PyNewProjectSettings; +import com.jetbrains.python.newProject.PythonBaseProjectGenerator; +import com.jetbrains.python.sdk.PyDetectedSdk; +import com.jetbrains.python.sdk.PySdkService; +import com.jetbrains.python.sdk.PythonSdkAdditionalData; +import com.jetbrains.python.sdk.PythonSdkType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class PyCharmNewProjectStep extends DefaultActionGroup implements DumbAware { + private static final Logger LOG = Logger.getInstance(PyCharmNewProjectStep.class); + + public PyCharmNewProjectStep(@Nullable final Runnable runnable) { + super("Select Project Type", true); + + final NullableConsumer callback = new NullableConsumer() { + @Override + public void consume(@Nullable AbstractProjectSettingsStep settingsStep) { + if (runnable != null) + runnable.run(); + if (settingsStep == null) return; + + Sdk sdk = settingsStep.getSdk(); + final Project project = ProjectManager.getInstance().getDefaultProject(); + final ProjectSdksModel model = PyConfigurableInterpreterList.getInstance(project).getModel(); + if (sdk instanceof PyDetectedSdk) { + final String name = sdk.getName(); + VirtualFile sdkHome = ApplicationManager.getApplication().runWriteAction(new Computable() { + @Override + public VirtualFile compute() { + return LocalFileSystem.getInstance().refreshAndFindFileByPath(name); + } + }); + PySdkService.getInstance().solidifySdk(sdk); + sdk = SdkConfigurationUtil.setupSdk(ProjectJdkTable.getInstance().getAllJdks(), sdkHome, PythonSdkType.getInstance(), true, null, + null); + model.addSdk(sdk); + try { + model.apply(); + } + catch (ConfigurationException exception) { + LOG.error("Error adding detected python interpreter " + exception.getMessage()); + } + } + Project newProject = generateProject(project, settingsStep); + if (newProject != null) { + SdkConfigurationUtil.setDirectoryProjectSdk(newProject, sdk); + final List sdks = PythonSdkType.getAllSdks(); + for (Sdk s : sdks) { + final SdkAdditionalData additionalData = s.getSdkAdditionalData(); + if (additionalData instanceof PythonSdkAdditionalData) { + ((PythonSdkAdditionalData)additionalData).reassociateWithCreatedProject(newProject); + } + } + } + } + + @Nullable + private Project generateProject(@NotNull final Project project, @NotNull final AbstractProjectSettingsStep settings) { + final DirectoryProjectGenerator generator = settings.getProjectGenerator(); + final File location = new File(settings.getProjectLocation()); + if (!location.exists() && !location.mkdirs()) { + Messages.showErrorDialog(project, "Cannot create directory '" + location + "'", "Create Project"); + return null; + } + + final VirtualFile baseDir = ApplicationManager.getApplication().runWriteAction(new Computable() { + public VirtualFile compute() { + return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(location); + } + }); + LOG.assertTrue(baseDir != null, "Couldn't find '" + location + "' in VFS"); + baseDir.refresh(false, true); + + if (baseDir.getChildren().length > 0) { + int rc = Messages.showYesNoDialog(project, + "The directory '" + location + + "' is not empty. Would you like to create a project from existing sources instead?", + "Create New Project", Messages.getQuestionIcon()); + if (rc == Messages.YES) { + return PlatformProjectOpenProcessor.getInstance().doOpenProject(baseDir, null, false); + } + } + + String generatorName = generator == null ? "empty" : ConvertUsagesUtil.ensureProperKey(generator.getName()); + UsageTrigger.trigger("NewDirectoryProjectAction." + generatorName); + + GeneralSettings.getInstance().setLastProjectCreationLocation(location.getParent()); + + return PlatformProjectOpenProcessor.doOpenProject(baseDir, null, false, -1, new ProjectOpenedCallback() { + @Override + public void projectOpened(Project project, Module module) { + if (generator != null) { + final Object projectSettings = generator.getProjectSettings(); + if (projectSettings instanceof PyNewProjectSettings) { + ((PyNewProjectSettings)projectSettings).setSdk(settings.getSdk()); + ((PyNewProjectSettings)projectSettings).setInstallFramework(settings.installFramework()); + } + //noinspection unchecked + generator.generateProject(project, baseDir, projectSettings, module); + } + } + }, false); + } + }; + + final ProjectSpecificAction action = new ProjectSpecificAction(callback, new PythonBaseProjectGenerator()); + add(action); + + final DirectoryProjectGenerator[] generators = Extensions.getExtensions(DirectoryProjectGenerator.EP_NAME); + Arrays.sort(generators, new Comparator() { + @Override + public int compare(DirectoryProjectGenerator o1, DirectoryProjectGenerator o2) { + if (o1 instanceof PyFrameworkProjectGenerator && !(o2 instanceof PyFrameworkProjectGenerator)) return -1; + if (!(o1 instanceof PyFrameworkProjectGenerator) && o2 instanceof PyFrameworkProjectGenerator) return 1; + return o1.getName().compareTo(o2.getName()); + } + }); + for (DirectoryProjectGenerator generator : generators) { + add(new ProjectSpecificAction(callback, generator)); + } + } + + public PyCharmNewProjectStep() { + this(null); + + } + +} diff --git a/python/openapi/src/com/jetbrains/python/newProject/PyFrameworkProjectGenerator.java b/python/openapi/src/com/jetbrains/python/newProject/PyFrameworkProjectGenerator.java index f9dc7a6b204d..08e3402386e9 100644 --- a/python/openapi/src/com/jetbrains/python/newProject/PyFrameworkProjectGenerator.java +++ b/python/openapi/src/com/jetbrains/python/newProject/PyFrameworkProjectGenerator.java @@ -15,7 +15,6 @@ */ package com.jetbrains.python.newProject; -import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.platform.DirectoryProjectGenerator; @@ -25,7 +24,7 @@ import com.intellij.platform.DirectoryProjectGenerator; public interface PyFrameworkProjectGenerator extends DirectoryProjectGenerator { String getFrameworkTitle(); - boolean isFrameworkInstalled(Project project, Sdk sdk); + boolean isFrameworkInstalled(Sdk sdk); boolean acceptsRemoteSdk(); diff --git a/python/src/META-INF/pycharm-core.xml b/python/src/META-INF/pycharm-core.xml index fdec83addc28..775d912b284a 100644 --- a/python/src/META-INF/pycharm-core.xml +++ b/python/src/META-INF/pycharm-core.xml @@ -79,7 +79,7 @@ - + @@ -95,7 +95,7 @@ icon="AllIcons.RunConfigurations.RerunFailedTests"/> - +