diff --git a/python/educational-core/educational-core.iml b/python/educational-core/educational-core.iml index 058f88e071e4..811dc90aff11 100644 --- a/python/educational-core/educational-core.iml +++ b/python/educational-core/educational-core.iml @@ -33,5 +33,6 @@ + \ No newline at end of file diff --git a/python/educational-core/resources/META-INF/plugin.xml b/python/educational-core/resources/META-INF/plugin.xml index 370643525c1d..5ebd039efd0c 100644 --- a/python/educational-core/resources/META-INF/plugin.xml +++ b/python/educational-core/resources/META-INF/plugin.xml @@ -173,6 +173,7 @@ + diff --git a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java index 2dc554e97479..d29ff113de48 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java @@ -14,6 +14,7 @@ import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; +import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; import com.jetbrains.edu.learning.actions.*; import com.jetbrains.edu.learning.checker.StudyTaskChecker; import com.jetbrains.edu.learning.core.EduNames; @@ -138,5 +139,7 @@ public interface EduPluginConfigurator { return Collections.emptyList(); } + EduCourseProjectGenerator getEduCourseProjectGenerator(); + default ModuleType getModuleType() {return StdModuleTypes.JAVA;} } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java index e2e84ae3ea67..16aa194a1ba4 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java @@ -21,6 +21,7 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; @@ -56,13 +57,14 @@ import java.util.List; import java.util.Map; import static com.jetbrains.edu.learning.StudyUtils.execCancelable; +import static com.jetbrains.edu.learning.StudyUtils.navigateToStep; public class StudyProjectComponent implements ProjectComponent { private static final Logger LOG = Logger.getInstance(StudyProjectComponent.class.getName()); private final Project myProject; private FileCreatedByUserListener myListener; - private Map>> myDeletedShortcuts = new HashMap<>(); + private final Map>> myDeletedShortcuts = new HashMap<>(); private MessageBusConnection myBusConnection; private StudyProjectComponent(@NotNull final Project project) { @@ -103,6 +105,29 @@ public class StudyProjectComponent implements ProjectComponent { } } }); + + selectStep(); + } + + private void selectStep() { + StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> { + Project defaultProject = ProjectManager.getInstance().getDefaultProject(); + if (myProject == defaultProject) { + return; + } + StudyTaskManager defaultTaskManager = StudyTaskManager.getInstance(defaultProject); + int stepId = defaultTaskManager.getStepId(); + + if (stepId != 0) { + StudyTaskManager taskManager = StudyTaskManager.getInstance(myProject); + Course course = taskManager.getCourse(); + if (course != null) { + + navigateToStep(myProject, course, stepId); + defaultTaskManager.setStepId(0); + } + } + }); } private void updateAvailable(Course course) { @@ -122,7 +147,6 @@ public class StudyProjectComponent implements ProjectComponent { }, "Updating Course", true, myProject); EduUtils.synchronize(); course.setUpdated(); - } }); notification.notify(myProject); @@ -272,7 +296,7 @@ public class StudyProjectComponent implements ProjectComponent { AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null); for (AnAction newAction : newGroupActions) { if (newAction == action) { - myListener = new FileCreatedByUserListener(); + myListener = new FileCreatedByUserListener(); VirtualFileManager.getInstance().addVirtualFileListener(myListener); break; } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java index d64896ca4bb8..210dd11c6a87 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java @@ -46,15 +46,16 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb private static final Logger LOG = Logger.getInstance(StudyTaskManager.class); public static final int CURRENT_VERSION = 5; private Course myCourse; - public int VERSION = 5; + public int VERSION = CURRENT_VERSION; - public Map> myUserTests = new HashMap<>(); + public final Map> myUserTests = new HashMap<>(); public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx(); private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT; private boolean myTurnEditingMode = false; @Transient private final Project myProject; + @Transient private int myStepId; public StudyTaskManager(Project project) { myProject = project; @@ -191,14 +192,18 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb private void updateTestHelper() { StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().runWriteAction(() -> { - final VirtualFile testHelper = myProject.getBaseDir().findChild(EduNames.TEST_HELPER); + final VirtualFile baseDir = myProject.getBaseDir(); + if (baseDir == null) { + return; + } + final VirtualFile testHelper = baseDir.findChild(EduNames.TEST_HELPER); if (testHelper != null) { StudyUtils.deleteFile(testHelper); } final FileTemplate template = FileTemplateManager.getInstance(myProject).getInternalTemplate(FileUtil.getNameWithoutExtension(EduNames.TEST_HELPER)); try { - final PsiDirectory projectDir = PsiManager.getInstance(myProject).findDirectory(myProject.getBaseDir()); + final PsiDirectory projectDir = PsiManager.getInstance(myProject).findDirectory(baseDir); if (projectDir != null) { FileTemplateUtil.createFromTemplate(template, EduNames.TEST_HELPER, null, projectDir); } @@ -211,8 +216,9 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb private void deserialize(Element state) throws StudySerializationUtils.StudyUnrecognizedFormatException { final Element taskManagerElement = state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT); - if (taskManagerElement == null) + if (taskManagerElement == null) { throw new StudySerializationUtils.StudyUnrecognizedFormatException(); + } XmlSerializer.deserializeInto(this, taskManagerElement); final Element xmlCourse = StudySerializationUtils.Xml.getChildWithName(taskManagerElement, StudySerializationUtils.COURSE); final Element remoteCourseElement = xmlCourse.getChild(REMOTE_COURSE); @@ -251,4 +257,11 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb myTurnEditingMode = turnEditingMode; } + public void setStepId(int stepId) { + myStepId = stepId; + } + + public int getStepId() { + return myStepId; + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java index 46d3ca1b1fc2..0b1b6bcf557e 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java @@ -84,6 +84,8 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import static com.jetbrains.edu.learning.navigation.StudyNavigator.navigateToTask; + public class StudyUtils { private StudyUtils() { } @@ -821,4 +823,25 @@ public class StudyUtils { } } } + + public static void navigateToStep(@NotNull Project project, @NotNull Course course, int stepId) { + if (stepId == 0 || course.isAdaptive()) { + return; + } + Task task = getTask(course, stepId); + if (task != null) { + navigateToTask(project, task); + } + } + + @Nullable + private static Task getTask(@NotNull Course course, int stepId) { + for (Lesson lesson : course.getLessons()) { + Task task = lesson.getTask(stepId); + if (task != null) { + return task; + } + } + return null; + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java new file mode 100644 index 000000000000..32408c5262ae --- /dev/null +++ b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java @@ -0,0 +1,161 @@ +/* + * Copyright 2000-2017 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.edu.learning.builtInServer; + +import com.intellij.ide.RecentProjectsManagerBase; +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.TransactionGuard; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.util.xmlb.XmlSerializationException; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.RemoteCourse; +import com.jetbrains.edu.learning.newproject.ui.EduCreateNewProjectDialog; +import com.jetbrains.edu.learning.newproject.ui.EduCreateNewStepikProjectDialog; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static com.jetbrains.edu.learning.StudyUtils.navigateToStep; +import static com.jetbrains.edu.learning.core.EduNames.STUDY_PROJECT_XML_PATH; + +public class EduBuiltInServerUtils { + public static boolean focusOpenProject(int courseId, int stepId) { + Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); + for (Project project : openProjects) { + if (!project.isDefault()) { + StudyTaskManager taskManager = StudyTaskManager.getInstance(project); + if (taskManager != null) { + Course course = taskManager.getCourse(); + RemoteCourse remoteCourse = course instanceof RemoteCourse ? (RemoteCourse)course : null; + if (remoteCourse != null && remoteCourse.getId() == courseId) { + ApplicationManager.getApplication().invokeLater(() -> { + requestFocus(project); + navigateToStep(project, course, stepId); + }); + return true; + } + } + } + } + return false; + } + + @Nullable + private static Project openProject(@NotNull String projectPath) { + final Project[] project = {null}; + ApplicationManager.getApplication().invokeAndWait(() -> { + TransactionGuard.getInstance().submitTransactionAndWait(() -> + project[0] = ProjectUtil.openProject(projectPath, null, true)); + requestFocus(project[0]); + }); + return project[0]; + } + + private static void requestFocus(@NotNull Project project) { + ProjectUtil.focusProjectWindow(project, false); + } + + public static boolean openRecentProject(int targetCourseId, int stepId) { + RecentProjectsManagerBase recentProjectsManager = RecentProjectsManagerBase.getInstanceEx(); + + if (recentProjectsManager == null) { + return false; + } + + RecentProjectsManagerBase.State state = recentProjectsManager.getState(); + + if (state == null) { + return false; + } + + List recentPaths = state.recentPaths; + + SAXBuilder parser = new SAXBuilder(); + + for (String projectPath : recentPaths) { + Element component = readComponent(parser, projectPath); + if (component == null) { + continue; + } + StudyTaskManager taskManager = getDefaultTaskManager(); + int courseId = getCourseId(taskManager, component); + + if (courseId == targetCourseId) { + taskManager.setStepId(stepId); + Project project = openProject(projectPath); + if (project != null) { + return true; + } + } + } + return false; + } + + + @Nullable + private static Element readComponent(@NotNull SAXBuilder parser, @NotNull String projectPath) { + Element component = null; + try { + String studyProjectXML = projectPath + STUDY_PROJECT_XML_PATH; + Document xmlDoc = parser.build(new File(studyProjectXML)); + Element root = xmlDoc.getRootElement(); + component = root.getChild("component"); + } + catch (JDOMException | IOException ignored) { + } + + return component; + } + + private static int getCourseId(@NotNull StudyTaskManager taskManager, @NotNull Element component) { + try { + taskManager.loadState(component); + Course course = taskManager.getCourse(); + + if (course instanceof RemoteCourse) { + return ((RemoteCourse)course).getId(); + } + } + catch (IllegalStateException | XmlSerializationException ignored) { + } + return 0; + } + + public static boolean createProject(int courseId, int stepId) { + ApplicationManager.getApplication().invokeLater(() -> { + getDefaultTaskManager().setStepId(stepId); + EduCreateNewProjectDialog createNewProjectDlg = new EduCreateNewStepikProjectDialog(courseId); + createNewProjectDlg.show(); + }); + + return true; + } + @NotNull + private static StudyTaskManager getDefaultTaskManager() { + Project defaultProject = ProjectManager.getInstance().getDefaultProject(); + return StudyTaskManager.getInstance(defaultProject); + } +} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java new file mode 100644 index 000000000000..245797f796aa --- /dev/null +++ b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java @@ -0,0 +1,97 @@ +/* + * Copyright 2000-2017 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.edu.learning.builtInServer; + +import com.intellij.openapi.diagnostic.Logger; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.QueryStringDecoder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.ide.RestService; + +import java.io.IOException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.jetbrains.edu.learning.builtInServer.EduBuiltInServerUtils.*; +import static com.jetbrains.edu.learning.stepic.EduStepicNames.EDU_STEPIK_SERVICE_NAME; +import static com.jetbrains.edu.learning.stepic.EduStepicNames.STEP_ID; + +public class EduStepikRestService extends RestService { + private static final Logger LOG = Logger.getInstance(EduStepikRestService.class.getName()); + private static final Pattern OPEN_COURSE_PATTERN = Pattern.compile("/" + EDU_STEPIK_SERVICE_NAME + "/course/(\\d+)"); + + @NotNull + @Override + protected String getServiceName() { + return EDU_STEPIK_SERVICE_NAME; + } + + @Override + protected boolean isMethodSupported(@NotNull HttpMethod method) { + return method == HttpMethod.GET; + } + + @Override + protected boolean isPrefixlessAllowed() { + return true; + } + + @Nullable + @Override + public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) + throws IOException { + LOG.info("Request: " + urlDecoder.uri()); + + String path = urlDecoder.path(); + Matcher matcher = OPEN_COURSE_PATTERN.matcher(path); + if (matcher.matches()) { + int courseId = Integer.parseInt(matcher.group(1)); + List stepIds = urlDecoder.parameters().get(STEP_ID); + int stepId = 0; + if (stepIds != null && !stepIds.isEmpty()) { + String firstStepId = ""; + try { + firstStepId = stepIds.get(0); + stepId = Integer.parseInt(firstStepId); + } catch (NumberFormatException e) { + LOG.warn("Wrong a request parameter: step_id=" + firstStepId, e); + } + } + LOG.info(String.format("Try to open a course: courseId=%s, stepId=%s", courseId, stepId)); + + if (focusOpenProject(courseId, stepId) || openRecentProject(courseId, stepId) || createProject(courseId, stepId)) { + RestService.sendOk(request, context); + LOG.info("Course opened: " + courseId); + return null; + } + + RestService.sendStatus(HttpResponseStatus.NOT_FOUND, false, context.channel()); + String message = "A project didn't found or created"; + LOG.info(message); + return message; + } + + RestService.sendStatus(HttpResponseStatus.BAD_REQUEST, false, context.channel()); + String message = "Unknown command: " + path; + LOG.info(message); + return message; + } +} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java b/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java index c2c35a63b217..db66e50205b5 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java @@ -50,6 +50,8 @@ public class EduNames { public static final String SUBTASK_MARKER = "_subtask"; public static final String SUBTASK = "subtask"; + public static final String STUDY_PROJECT_XML_PATH = "/.idea/study_project.xml"; + private EduNames() { } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java index 1fed74118ab7..d26e77110c00 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java @@ -41,7 +41,7 @@ public class StudyGenerator { } else { String lessonDirName = EduNames.LESSON + Integer.toString(lesson.getIndex()); - VirtualFile lessonDir = courseDir.createChildDirectory(courseDir, lessonDirName); + VirtualFile lessonDir = VfsUtil.createDirectoryIfMissing(courseDir, lessonDirName); final List taskList = lesson.getTaskList(); for (int i = 1; i <= taskList.size(); i++) { Task task = taskList.get(i - 1); @@ -52,11 +52,12 @@ public class StudyGenerator { } public static void createTask(@NotNull final Task task, @NotNull final VirtualFile lessonDir) throws IOException { - VirtualFile taskDir = lessonDir.createChildDirectory(lessonDir, EduNames.TASK + Integer.toString(task.getIndex())); + String name = EduNames.TASK + Integer.toString(task.getIndex()); + VirtualFile taskDir = VfsUtil.createDirectoryIfMissing(lessonDir, name); createTaskContent(task, taskDir); } - public static void createTaskContent(@NotNull Task task, VirtualFile taskDir) throws IOException { + public static void createTaskContent(@NotNull Task task, @NotNull VirtualFile taskDir) throws IOException { int i = 0; for (Map.Entry taskFile : task.getTaskFiles().entrySet()) { TaskFile taskFileContent = taskFile.getValue(); @@ -73,25 +74,28 @@ public class StudyGenerator { createChildFile(taskDir, name, taskFile.text); } - private static void createDescriptions(VirtualFile taskDir, Task task) throws IOException { + private static void createDescriptions(@NotNull VirtualFile taskDir, @NotNull Task task) throws IOException { final Map texts = task.getTaskTexts(); + createFiles(taskDir, texts); + } + + private static void createTestFiles(@NotNull VirtualFile taskDir, @NotNull Task task) throws IOException { + final Map tests = task.getTestsText(); + createFiles(taskDir, tests); + } + + private static void createFiles(@NotNull VirtualFile taskDir, @NotNull Map texts) throws IOException { for (Map.Entry entry : texts.entrySet()) { final String name = entry.getKey(); - final VirtualFile virtualTaskFile = taskDir.createChildData(taskDir, name); + VirtualFile virtualTaskFile = taskDir.findChild(name); + if (virtualTaskFile == null) { + virtualTaskFile = taskDir.createChildData(taskDir, name); + } VfsUtil.saveText(virtualTaskFile, entry.getValue()); } } - private static void createTestFiles(VirtualFile taskDir, Task task) throws IOException { - final Map tests = task.getTestsText(); - for (Map.Entry entry : tests.entrySet()) { - final String name = entry.getKey(); - final VirtualFile virtualTaskFile = taskDir.createChildData(taskDir, name); - VfsUtil.saveText(virtualTaskFile, entry.getValue()); - } - } - - private static void createAdditionalFiles(Lesson lesson, VirtualFile courseDir) throws IOException { + private static void createAdditionalFiles(@NotNull Lesson lesson, @NotNull VirtualFile courseDir) throws IOException { final List taskList = lesson.getTaskList(); if (taskList.size() != 1) return; final Task task = taskList.get(0); @@ -100,8 +104,7 @@ public class StudyGenerator { } } - - public static void createChildFile(@NotNull VirtualFile taskDir, String name, String text) throws IOException { + public static void createChildFile(@NotNull VirtualFile taskDir, @NotNull String name, @NotNull String text) throws IOException { String newDirectories = null; String fileName = name; VirtualFile dir = taskDir; @@ -114,7 +117,10 @@ public class StudyGenerator { dir = VfsUtil.createDirectoryIfMissing(taskDir, newDirectories); } if (dir != null) { - final VirtualFile virtualTaskFile = dir.createChildData(taskDir, fileName); + VirtualFile virtualTaskFile = dir.findChild(fileName); + if (virtualTaskFile == null) { + virtualTaskFile = dir.createChildData(taskDir, fileName); + } if (EduUtils.isImage(name)) { virtualTaskFile.setBinaryContent(Base64.decodeBase64(text)); } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java b/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java index 85b709a9dc63..d5b42cd248eb 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java @@ -1,6 +1,7 @@ package com.jetbrains.edu.learning.navigation; import com.intellij.ide.projectView.ProjectView; +import com.intellij.ide.projectView.impl.AbstractProjectViewPane; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -190,7 +191,11 @@ public class StudyNavigator { } private static void updateProjectView(@NotNull Project project, @NotNull VirtualFile fileToActivate) { - JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree(); + AbstractProjectViewPane viewPane = ProjectView.getInstance(project).getCurrentProjectViewPane(); + if (viewPane == null) { + return; + } + JTree tree = viewPane.getTree(); ProjectView.getInstance(project).selectCB(fileToActivate, fileToActivate, false).doWhenDone(() -> { List paths = TreeUtil.collectExpandedPaths(tree); List toCollapse = new ArrayList<>(); diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java new file mode 100644 index 000000000000..0a0b04f5c09d --- /dev/null +++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2017 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.edu.learning.newproject; + +import com.intellij.facet.ui.ValidationResult; +import com.intellij.openapi.project.Project; +import com.intellij.platform.DirectoryProjectGenerator; +import com.jetbrains.edu.learning.courseFormat.Course; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface EduCourseProjectGenerator { + @NotNull + DirectoryProjectGenerator getDirectoryProjectGenerator(); + + @Nullable + Object getProjectSettings(); + + void setCourse(@NotNull Course course); + + ValidationResult validate(); + + boolean beforeProjectGenerated(); + + void afterProjectGenerated(@NotNull Project project); +} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java new file mode 100644 index 000000000000..9d7c42ca66fe --- /dev/null +++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java @@ -0,0 +1,176 @@ +/* + * Copyright 2000-2017 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.edu.learning.newproject.ui; + +import com.intellij.facet.ui.ValidationResult; +import com.intellij.ide.RecentProjectsManager; +import com.intellij.internal.statistic.UsageTrigger; +import com.intellij.internal.statistic.beans.ConvertUsagesUtil; +import com.intellij.lang.Language; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.platform.DirectoryProjectGenerator; +import com.intellij.platform.PlatformProjectOpenProcessor; +import com.intellij.platform.templates.TemplateProjectDirectoryGenerator; +import com.intellij.projectImport.ProjectOpenedCallback; +import com.jetbrains.edu.learning.EduPluginConfigurator; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.File; +import java.util.EnumSet; + +public class EduCreateNewProjectDialog extends DialogWrapper { + private static final Logger LOG = Logger.getInstance(EduCreateNewProjectDialog.class); + protected Project myProject; + protected Course myCourse; + private final EduCreateNewProjectPanel myPanel; + + public EduCreateNewProjectDialog() { + super(false); + setTitle("New Project"); + Project defaultProject = ProjectManager.getInstance().getDefaultProject(); + myPanel = new EduCreateNewProjectPanel(defaultProject, this); + setOKButtonText("Create"); + init(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myPanel; + } + + public void setCourse(@Nullable Course course) { + myCourse = course; + String description = course != null ? course.getDescription() : ""; + myPanel.setDescription(description); + } + + @Override + protected void doOKAction() { + if (myCourse == null) { + myPanel.setError("Selected course is null"); + return; + } + Language language = myCourse.getLanguageById(); + if (language == null) { + String message = "Selected course don't have language"; + myPanel.setError(message); + LOG.warn(message); + return; + } + EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); + if (configurator == null) { + String message = "A configurator for the selected course not found"; + myPanel.setError(message); + LOG.warn(message + ": " + language); + return; + } + EduCourseProjectGenerator projectGenerator = configurator.getEduCourseProjectGenerator(); + String errorMessage = createProject(projectGenerator); + if (errorMessage != null) { + myPanel.setError(errorMessage); + return; + } + if (myProject == null) { + myPanel.setError("Project did't created"); + return; + } + super.doOKAction(); + } + + /** + * @param projectGenerator + * @return error message if didn't create project else return null + */ + @Nullable + private String createProject(@NotNull final EduCourseProjectGenerator projectGenerator) { + String location = FileUtil.toSystemDependentName(myPanel.getLocationPath()); + + ValidationResult result = projectGenerator.validate(); + if (!result.isOk()) { + return result.getErrorMessage(); + } + + final File directory = new File(location); + if (!FileUtil.createDirectory(directory)) { + String message = "Can't create a project directory"; + LOG.error(message + ": " + location); + return message; + } + + projectGenerator.setCourse(myCourse); + + final VirtualFile baseDir = ApplicationManager.getApplication() + .runWriteAction((Computable)() -> + LocalFileSystem.getInstance().refreshAndFindFileByIoFile(directory) + ); + + if (baseDir == null) { + LOG.error("Couldn't find '" + directory + "' in VFS"); + return "Couldn't find in VFS"; + } + VfsUtil.markDirtyAndRefresh(false, true, true, baseDir); + + if (baseDir.getChildren().length > 0) { + String message = + String.format("Directory '%s' is not empty.\nFiles and directories will remove.\nDo you want continue?", + directory.getAbsolutePath()); + int rc = Messages.showYesNoDialog((Project)null, message, "New Project", Messages.getQuestionIcon()); + if (rc != Messages.YES) { + myPanel.resetError(); + return "Canceled by user"; + } + } + + DirectoryProjectGenerator generator = projectGenerator.getDirectoryProjectGenerator(); + + String generatorName = ConvertUsagesUtil.ensureProperKey(generator.getName()); + UsageTrigger.trigger("AbstractNewProjectStep." + generatorName); + + RecentProjectsManager.getInstance().setLastProjectCreationLocation(directory.getParent()); + + ProjectOpenedCallback callback = null; + if(generator instanceof TemplateProjectDirectoryGenerator){ + ((TemplateProjectDirectoryGenerator)generator).generateProject(baseDir.getName(), location); + } else { + callback = (project, module) -> { + if (projectGenerator.beforeProjectGenerated()) { + Object settings = projectGenerator.getProjectSettings(); + //noinspection unchecked + generator.generateProject(project, baseDir, settings, module); + projectGenerator.afterProjectGenerated(project); + } + }; + } + EnumSet options = EnumSet.noneOf(PlatformProjectOpenProcessor.Option.class); + myProject = PlatformProjectOpenProcessor.doOpenProject(baseDir, null, -1, callback, options); + return null; + } +} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form new file mode 100644 index 000000000000..7a771f682f0f --- /dev/null +++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form @@ -0,0 +1,71 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java new file mode 100644 index 000000000000..d9e71daa53d3 --- /dev/null +++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2017 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.edu.learning.newproject.ui; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapperPeer; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.io.FileUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.io.File; + +public class EduCreateNewProjectPanel extends JPanel { + private JPanel myPanel; + private TextFieldWithBrowseButton myLocationField; + private JLabel myErrorIcon; + private JLabel myErrorLabel; + private JTextPane myDescription; + + public EduCreateNewProjectPanel(@NotNull final Project project, @NotNull EduCreateNewProjectDialog dialog) { + setLayout(new BorderLayout()); + add(myPanel, BorderLayout.CENTER); + myErrorIcon.setIcon(AllIcons.Actions.Lightning); + resetError(); + String location = findSequentNonExistingUntitled().toString(); + myLocationField.setText(location); + final int index = location.lastIndexOf(File.separator); + if (index > 0) { + JTextField textField = myLocationField.getTextField(); + textField.select(index + 1, location.length()); + textField.putClientProperty(DialogWrapperPeer.HAVE_INITIAL_SELECTION, true); + } + + final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + myLocationField.addBrowseFolderListener("Select Base Directory", + "Select base directory for the project", + project, + descriptor); + + myLocationField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + String location = FileUtil.toSystemDependentName(myLocationField.getText()); + File file = new File(location); + if (!FileUtil.ensureCanCreateFile(file)) { + dialog.setOKActionEnabled(false); + setError("Invalid location"); + } else { + dialog.setOKActionEnabled(true); + resetError(); + } + } + }); + } + + @NotNull + protected File findSequentNonExistingUntitled() { + return FileUtil.findSequentNonexistentFile(new File(ProjectUtil.getBaseDir()), "course", ""); + } + + private void setState(boolean isVisible) { + myErrorIcon.setVisible(isVisible); + myErrorLabel.setVisible(isVisible); + } + + void setError(@NotNull String message) { + myErrorLabel.setText(message); + setState(true); + } + + public String getLocationPath() { + return myLocationField.getText(); + } + + public void resetError() { + setState(false); + } + + public void setDescription(@NotNull String description) { + myDescription.setText(description); + } +} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java new file mode 100644 index 000000000000..7db74ffde68d --- /dev/null +++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2017 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.edu.learning.newproject.ui; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.stepic.EduStepicAuthorizedClient; +import com.jetbrains.edu.learning.stepic.EduStepicConnector; +import com.jetbrains.edu.learning.stepic.StepicUser; + +import java.io.IOException; + +import static com.jetbrains.edu.learning.StudyUtils.execCancelable; + +public class EduCreateNewStepikProjectDialog extends EduCreateNewProjectDialog { + private static final Logger LOG = Logger.getInstance(EduCreateNewStepikProjectDialog.class); + + public EduCreateNewStepikProjectDialog() { + super(); + } + + public EduCreateNewStepikProjectDialog(int courseId) { + this(); + + StepicUser user = EduStepicAuthorizedClient.getCurrentUser(); + Project defaultProject = ProjectManager.getInstance().getDefaultProject(); + ApplicationManager.getApplication().invokeAndWait(() -> + ProgressManager.getInstance() + .runProcessWithProgressSynchronously(() -> { + ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); + execCancelable(() -> { + try { + Course course = EduStepicConnector.getCourseFromStepik(user, courseId); + if (course != null) { + setTitle("New Project - " + course.getName()); + } + + setCourse(course); + } + catch (IOException e) { + LOG.warn("Tried to create a project for course with id=" + courseId, e); + } + return null; + }); + }, "Getting Available Courses", true, defaultProject) + ); + } + + @Override + public void show() { + if (myCourse != null) { + super.show(); + } else { + doCancelAction(); + } + } +} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index 5e7726077452..d9765b2b045a 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -127,6 +127,17 @@ public class EduStepicConnector { return null; } + private static StepicWrappers.CoursesContainer getCoursesFromStepik(@Nullable StepicUser user, URI url) throws IOException { + final StepicWrappers.CoursesContainer coursesContainer; + if (user != null) { + coursesContainer = EduStepicAuthorizedClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class, user); + } + else { + coursesContainer = EduStepicClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class); + } + return coursesContainer; + } + private static boolean addCoursesFromStepic(@Nullable StepicUser user, List result, int pageNumber) throws IOException { final URI url; try { @@ -137,17 +148,31 @@ public class EduStepicConnector { LOG.error(e.getMessage()); return false; } - final StepicWrappers.CoursesContainer coursesContainer; - if (user != null) { - coursesContainer = EduStepicAuthorizedClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class, user); - } - else { - coursesContainer = EduStepicClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class); - } + final StepicWrappers.CoursesContainer coursesContainer = getCoursesFromStepik(user, url); addAvailableCourses(result, coursesContainer); return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE; } + @Nullable + public static Course getCourseFromStepik(@Nullable StepicUser user, int courseId) throws IOException { + final URI url; + try { + url = new URIBuilder(EduStepicNames.COURSES + "/" + courseId).addParameter("is_idea_compatible", "true") + .build(); + } + catch (URISyntaxException e) { + LOG.error(e.getMessage()); + return null; + } + final StepicWrappers.CoursesContainer coursesContainer = getCoursesFromStepik(user, url); + + if (coursesContainer!= null && !coursesContainer.courses.isEmpty()) { + return coursesContainer.courses.get(0); + } else { + return null; + } + } + static void addAvailableCourses(List result, StepicWrappers.CoursesContainer coursesContainer) throws IOException { final List courses = coursesContainer.courses; for (RemoteCourse info : courses) { diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java index 5ee99dc6cde8..e2e8ef92ec1f 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java @@ -23,4 +23,7 @@ public class EduStepicNames { public static final String STEPIC_REGISTRATION_LINK = "https://stepic.org/registration?next=%2Fexplore%2Fcourses"; public static final String PYCHARM_PREFIX = "pycharm"; + + public static final String EDU_STEPIK_SERVICE_NAME = "edu/stepik"; + public static final String STEP_ID = "step_id"; } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index f7eb72f4b607..6945d806c7c3 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -333,10 +333,6 @@ public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor { myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : ""); } - public JComboBox getCoursesComboBox() { - return myCoursesComboBox; - } - public JPanel getInfoPanel() { return myInfoPanel; } diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java index bd224e42f31c..fad33f310523 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java @@ -25,6 +25,7 @@ import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; +import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; import com.jetbrains.python.PythonModuleTypeBase; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -151,6 +152,11 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator { return Collections.singletonList(new File(new File(jarPath, "courses"), COURSE_NAME).getPath()); } + @Override + public EduCourseProjectGenerator getEduCourseProjectGenerator() { + return new PyStudyDirectoryProjectGenerator(); + } + public ModuleType getModuleType() { return PythonModuleTypeBase.getInstance(); } diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java index 7b4b40317bea..ce401b6100eb 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java @@ -18,8 +18,10 @@ import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkAdditionalData; import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl; +import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil; import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.platform.DirectoryProjectGenerator; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiManager; import com.intellij.util.BooleanFunction; @@ -27,6 +29,7 @@ import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.RemoteCourse; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; +import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; import com.jetbrains.edu.learning.stepic.EduStepicConnector; import com.jetbrains.edu.learning.ui.StudyNewProjectPanel; import com.jetbrains.python.configuration.PyConfigurableInterpreterList; @@ -38,6 +41,7 @@ import com.jetbrains.python.remote.PyProjectSynchronizer; import com.jetbrains.python.sdk.AbstractCreateVirtualEnvDialog; import com.jetbrains.python.sdk.PyDetectedSdk; import com.jetbrains.python.sdk.PythonSdkAdditionalData; +import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; import icons.InteractiveLearningPythonIcons; import org.jetbrains.annotations.Nls; @@ -51,13 +55,13 @@ import java.awt.event.MouseEvent; import java.util.Collection; import java.util.List; - -public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator { +public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator + implements EduCourseProjectGenerator { private static final Logger LOG = Logger.getInstance(PyStudyDirectoryProjectGenerator.class.getName()); private final StudyProjectGenerator myGenerator; private static final String NO_PYTHON_INTERPRETER = "Add python interpreter."; + private final boolean isLocal; public ValidationResult myValidationResult = new ValidationResult("selected course is not valid"); - private StudyNewProjectPanel mySettingsPanel; @SuppressWarnings("unused") // used on startup public PyStudyDirectoryProjectGenerator() { @@ -65,6 +69,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator fireStateChanged()); - } - }); - - addErrorLabelMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - final Object selectedItem = mySettingsPanel.getCoursesComboBox().getSelectedItem(); - if (selectedItem != null && ((Course)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) { - mySettingsPanel.showLoginDialog(false, "Signing In"); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - final Object selectedItem = mySettingsPanel.getCoursesComboBox().getSelectedItem(); - if (selectedItem != null && ((Course)selectedItem).isAdaptive() && !myGenerator.isLoggedIn()) { - e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } - } - - @Override - public void mouseExited(MouseEvent e) { - final Course selectedItem = (Course)mySettingsPanel.getCoursesComboBox().getSelectedItem(); - if (selectedItem != null && selectedItem.isAdaptive() && !myGenerator.isLoggedIn()) { - e.getComponent().setCursor(Cursor.getDefaultCursor()); - } - } - }); } @Nls @@ -131,6 +99,10 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator sdks = PyConfigurableInterpreterList.getInstance(project).getAllPythonSdks(); + + ValidationResult validationResult; if (sdks.isEmpty()) { - myValidationResult = new ValidationResult(NO_PYTHON_INTERPRETER); + validationResult = new ValidationResult(NO_PYTHON_INTERPRETER); + } else { + validationResult = ValidationResult.OK; + } + + return validationResult; + } + + @NotNull + @Override + public ValidationResult validate(@NotNull String s) { + ValidationResult validationResult = validate(); + if (!validationResult.isOk()) { + myValidationResult = validationResult; } return myValidationResult; } + @Override + public boolean beforeProjectGenerated() { + BooleanFunction function = beforeProjectGenerated(null); + return function != null && function.fun(this); + } + + @Override + public void afterProjectGenerated(@NotNull Project project) { + PyNewProjectSettings settings = (PyNewProjectSettings)getProjectSettings(); + Sdk sdk = settings.getSdk(); + + if (sdk == null) { + createAndAddVirtualEnv(project, settings); + sdk = settings.getSdk(); + } + + SdkConfigurationUtil.setDirectoryProjectSdk(project, sdk); + final List sdks = PythonSdkType.getAllSdks(); + for (Sdk s : sdks) { + final SdkAdditionalData additionalData = s.getSdkAdditionalData(); + if (additionalData instanceof PythonSdkAdditionalData) { + ((PythonSdkAdditionalData)additionalData).reassociateWithCreatedProject(project); + } + } + } + public void setValidationResult(ValidationResult validationResult) { myValidationResult = validationResult; } @@ -163,10 +180,49 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator fireStateChanged()); + } + }); + + addErrorLabelMouseListener(new MouseAdapter() { + private boolean isCourseAdaptiveAndNotLogged() { + Course course = myGenerator.getSelectedCourse(); + return course != null && course.isAdaptive() && !myGenerator.isLoggedIn(); + } + + @Override + public void mouseClicked(MouseEvent e) { + if (isCourseAdaptiveAndNotLogged()) { + mySettingsPanel.showLoginDialog(false, "Signing In"); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + if (isCourseAdaptiveAndNotLogged()) { + e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + } + + @Override + public void mouseExited(MouseEvent e) { + if (isCourseAdaptiveAndNotLogged()) { + e.getComponent().setCursor(Cursor.getDefaultCursor()); + } + } + }); + return mySettingsPanel; } - public void setSelectedCourse(Course course) { + public void setCourse(@NotNull Course course) { myGenerator.setSelectedCourse(course); } @@ -184,7 +240,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator beforeProjectGenerated(@Nullable Sdk sdk) { return generator -> { final List enrolledCoursesIds = myGenerator.getEnrolledCoursesIds(); - final Course course = (Course)mySettingsPanel.getCoursesComboBox().getSelectedItem(); + final Course course = myGenerator.getSelectedCourse(); if (course == null || !(course instanceof RemoteCourse)) return true; if (((RemoteCourse)course).getId() > 0 && !enrolledCoursesIds.contains(((RemoteCourse)course).getId())) { ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { @@ -192,7 +248,6 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator EduStepicConnector.enrollToCourse(((RemoteCourse)course).getId(), StudySettings.getInstance().getUser())); }, "Creating Course", true, ProjectManager.getInstance().getDefaultProject()); - } return true; }; diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java index d2564a08b358..35ca0b58ad70 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyIntroductionCourseAction.java @@ -76,7 +76,7 @@ public class PyStudyIntroductionCourseAction extends AnAction { final ProjectSpecificSettingsStep step = new ProjectSpecificSettingsStep(generator, callback); step.createPanel(); // initialize panel to set location step.setLocation(projectDir.toString()); - generator.setSelectedCourse(introCourse); + generator.setCourse(introCourse); callback.consume(step); }