| 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; |
| } |
| |
| } |