blob: 6514741fda4528f840a65ff9034c465aa002b683 [file] [log] [blame]
package com.jetbrains.python.newProject.actions;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.icons.AllIcons;
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.extensions.Extensions;
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.MessageType;
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.JBScrollPane;
import com.intellij.util.NullableConsumer;
import com.intellij.util.ui.UIUtil;
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.newProject.PythonProjectGenerator;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import icons.PythonIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
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<AbstractProjectSettingsStep> myCallback;
private final boolean myIsWelcomeScreen;
private PythonSdkChooserCombo mySdkCombo;
private boolean myInstallFramework;
private TextFieldWithBrowseButton myLocationField;
protected final File myProjectDirectory;
private Button myCreateButton;
private JLabel myErrorLabel;
private AnAction myCreateAction;
private Sdk mySdk;
public AbstractProjectSettingsStep(DirectoryProjectGenerator projectGenerator,
NullableConsumer<AbstractProjectSettingsStep> callback,
boolean isWelcomeScreen) {
super();
myProjectGenerator = projectGenerator;
myCallback = callback;
myIsWelcomeScreen = isWelcomeScreen;
myProjectDirectory = FileUtil.findSequentNonexistentFile(new File(ProjectUtil.getBaseDir()), "untitled", "");
myCreateAction = new AnAction("Create", "Create Project", getIcon()) {
@Override
public void actionPerformed(AnActionEvent e) {
boolean isValid = checkValid();
if (isValid && myCallback != null)
myCallback.consume(AbstractProjectSettingsStep.this);
}
};
}
@Override
public void actionPerformed(AnActionEvent e) {
}
@Override
public JPanel createPanel() {
initGeneratorListeners();
final JPanel basePanel = createBasePanel();
final JPanel mainPanel = new JPanel(new BorderLayout());
final JPanel scrollPanel = new JPanel(new BorderLayout());
final DirectoryProjectGenerator[] generators = Extensions.getExtensions(DirectoryProjectGenerator.EP_NAME);
final int height = generators.length == 0 && !myIsWelcomeScreen ? 150 : 400;
mainPanel.setPreferredSize(new Dimension(mainPanel.getPreferredSize().width, height));
myErrorLabel = new JLabel("");
myErrorLabel.setForeground(JBColor.RED);
myCreateButton = new Button(myCreateAction, myCreateAction.getTemplatePresentation());
scrollPanel.add(basePanel, BorderLayout.NORTH);
final JPanel advancedSettings = createAdvancedSettings();
if (advancedSettings != null) {
scrollPanel.add(advancedSettings, BorderLayout.CENTER);
}
final JBScrollPane scrollPane = new JBScrollPane(scrollPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBorder(null);
mainPanel.add(scrollPane, BorderLayout.CENTER);
final JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.add(myErrorLabel, BorderLayout.NORTH);
bottomPanel.add(myCreateButton, BorderLayout.EAST);
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
return mainPanel;
}
private void initGeneratorListeners() {
if (myProjectGenerator instanceof WebProjectTemplate) {
((WebProjectTemplate)myProjectGenerator).getPeer().addSettingsStateListener(new WebProjectGenerator.SettingsStateListener() {
@Override
public void stateChanged(boolean validSettings) {
checkValid();
}
});
}
else if (myProjectGenerator instanceof PythonProjectGenerator) {
((PythonProjectGenerator)myProjectGenerator).addSettingsStateListener(new PythonProjectGenerator.SettingsListener() {
@Override
public void stateChanged() {
checkValid();
}
});
}
}
protected Icon getIcon() {
return myProjectGenerator.getLogo();
}
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);
myLocationField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
if (myProjectGenerator instanceof PythonProjectGenerator) {
String path = myLocationField.getText().trim();
if (path.endsWith(File.separator)) {
path = path.substring(0, path.length() - File.separator.length());
}
int ind = path.lastIndexOf(File.separator);
if (ind != -1) {
String projectName = path.substring(ind + 1, path.length());
((PythonProjectGenerator)myProjectGenerator).locationChanged(projectName);
}
}
}
});
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<Sdk> sdks = PyConfigurableInterpreterList.getInstance(project).getAllPythonSdks();
VirtualEnvProjectFilter.removeAllAssociated(sdks);
Sdk compatibleSdk = sdks.isEmpty() ? null : sdks.iterator().next();
DirectoryProjectGenerator generator = getProjectGenerator();
if (generator instanceof PyFrameworkProjectGenerator && !((PyFrameworkProjectGenerator)generator).supportsPython3()) {
if (compatibleSdk != null && PythonSdkType.getLanguageLevelForSdk(compatibleSdk).isPy3K()) {
Sdk python2Sdk = PythonSdkType.findPython2Sdk(sdks);
if (python2Sdk != null) {
compatibleSdk = python2Sdk;
}
}
}
final Sdk preferred = compatibleSdk;
mySdkCombo = new PythonSdkChooserCombo(project, sdks, new Condition<Sdk>() {
@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);
final JPanel basePanelExtension = extendBasePanel();
if (basePanelExtension != null) {
c.gridwidth = 2;
c.gridy = 2;
c.gridx = 0;
panel.add(basePanelExtension, c);
}
registerValidators();
return panel;
}
@Nullable
protected JPanel extendBasePanel() {
if (myProjectGenerator instanceof PythonProjectGenerator)
return ((PythonProjectGenerator)myProjectGenerator).extendBasePanel();
return null;
}
protected void registerValidators() {
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() {
if (myLocationField == null) return true;
final String projectName = myLocationField.getText();
setErrorText(null);
myInstallFramework = false;
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 PythonProjectGenerator) {
final ValidationResult warningResult = ((PythonProjectGenerator)myProjectGenerator).warningValidation(getSdk());
if (!warningResult.isOk()) {
setWarningText(warningResult.getErrorMessage());
}
}
if (myProjectGenerator instanceof WebProjectTemplate) {
final WebProjectGenerator.GeneratorPeer peer = ((WebProjectTemplate)myProjectGenerator).getPeer();
final ValidationInfo validationInfo = peer.validate();
if (validationInfo != null && !peer.isBackgroundJobRunning()) {
setErrorText(validationInfo.message);
return false;
}
}
}
final Sdk sdk = getSdk();
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)) {
String warningText = frameworkName + " will be installed on selected interpreter";
myInstallFramework = true;
final PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
if (!packageManager.hasManagement(PySdkUtil.isRemote(sdk))) {
warningText = "Python packaging tools and " + warningText;
}
setWarningText(warningText);
}
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);
myErrorLabel.setForeground(MessageType.ERROR.getTitleForeground());
myErrorLabel.setIcon(text == null ? null : AllIcons.Actions.Lightning);
myCreateButton.setEnabled(text == null);
}
public void setWarningText(@Nullable String text) {
myErrorLabel.setText("Note: " + text + " ");
myErrorLabel.setForeground(MessageType.WARNING.getTitleForeground());
}
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 final Border myBorder;
public Button(AnAction action, Presentation presentation) {
super(action, presentation, "NewProject", new Dimension(70, 50));
final Border border = new LineBorder(JBColor.border(), 1, true);
myBorder = UIUtil.isUnderDarcula() ? UIUtil.getButtonBorder() : border;
setBorder(myBorder);
}
@Override
protected int iconTextSpace() {
return 8;
}
@Override
public boolean isFocusable() {
return true;
}
@Override
protected void processFocusEvent(FocusEvent e) {
super.processFocusEvent(e);
if (e.getID() == FocusEvent.FOCUS_GAINED) {
processMouseEvent(new MouseEvent(this, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, 0, 0, 0, false));
}
else if (e.getID() == FocusEvent.FOCUS_LOST) {
processMouseEvent(new MouseEvent(this, MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, 0, 0, 0, false));
}
}
@Override
public Insets getInsets() {
return new Insets(5,10,5,5);
}
@Override
protected int horizontalTextAlignment() {
return SwingConstants.LEFT;
}
protected void processMouseEvent(MouseEvent e) {
super.processMouseEvent(e);
if (e.getID() == MouseEvent.MOUSE_ENTERED) {
setBorder(null);
}
else if (e.getID() == MouseEvent.MOUSE_EXITED) {
setBorder(myBorder);
}
}
}
public Sdk getSdk() {
if (mySdk != null) return mySdk;
return (Sdk)mySdkCombo.getComboBox().getSelectedItem();
}
public void setSdk(final Sdk sdk) {
mySdk = sdk;
}
public String getProjectLocation() {
return myLocationField.getText();
}
public void setLocation(@NotNull final String location) {
myLocationField.setText(location);
}
public boolean installFramework() {
return myInstallFramework;
}
}