1 package com.jetbrains.edu.learning;
3 import com.intellij.ide.ui.UISettings;
4 import com.intellij.notification.Notification;
5 import com.intellij.notification.NotificationListener;
6 import com.intellij.notification.NotificationType;
7 import com.intellij.openapi.actionSystem.*;
8 import com.intellij.openapi.actionSystem.ex.AnActionListener;
9 import com.intellij.openapi.application.ApplicationManager;
10 import com.intellij.openapi.components.ProjectComponent;
11 import com.intellij.openapi.diagnostic.Logger;
12 import com.intellij.openapi.editor.EditorFactory;
13 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
14 import com.intellij.openapi.keymap.Keymap;
15 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
16 import com.intellij.openapi.module.Module;
17 import com.intellij.openapi.module.ModuleManager;
18 import com.intellij.openapi.progress.ProgressManager;
19 import com.intellij.openapi.project.DumbAwareRunnable;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.Pair;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import com.intellij.openapi.vfs.VirtualFileAdapter;
25 import com.intellij.openapi.vfs.VirtualFileEvent;
26 import com.intellij.openapi.vfs.VirtualFileManager;
27 import com.intellij.openapi.wm.ToolWindow;
28 import com.intellij.openapi.wm.ToolWindowManager;
29 import com.intellij.util.containers.hash.HashMap;
30 import com.jetbrains.edu.learning.actions.StudyActionWithShortcut;
31 import com.jetbrains.edu.learning.actions.StudyNextWindowAction;
32 import com.jetbrains.edu.learning.actions.StudyPrevWindowAction;
33 import com.jetbrains.edu.learning.core.EduNames;
34 import com.jetbrains.edu.learning.core.EduUtils;
35 import com.jetbrains.edu.learning.courseFormat.*;
36 import com.jetbrains.edu.learning.editor.StudyEditorFactoryListener;
37 import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
38 import com.jetbrains.edu.learning.stepic.CourseInfo;
39 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
40 import com.jetbrains.edu.learning.ui.StudyToolWindow;
41 import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
42 import javafx.application.Platform;
43 import org.jetbrains.annotations.NotNull;
46 import javax.swing.event.HyperlinkEvent;
48 import java.io.IOException;
49 import java.util.ArrayList;
50 import java.util.List;
53 import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
54 import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.flushCourse;
57 public class StudyProjectComponent implements ProjectComponent {
58 private static final Logger LOG = Logger.getInstance(StudyProjectComponent.class.getName());
59 private final Project myProject;
60 private FileCreatedByUserListener myListener;
61 private Map<Keymap, List<Pair<String, String>>> myDeletedShortcuts = new HashMap<Keymap, List<Pair<String, String>>>();
62 private StudyProjectComponent(@NotNull final Project project) {
67 public void projectOpened() {
68 final Course course = StudyTaskManager.getInstance(myProject).getCourse();
69 // Check if user has javafx lib in his JDK. Now bundled JDK doesn't have this lib inside.
70 if (StudyUtils.hasJavaFx()) {
71 Platform.setImplicitExit(false);
74 if (course != null && !course.isUpToDate()) {
75 final Notification notification =
76 new Notification("Update.course", "Course Updates", "Course is ready to <a href=\"update\">update</a>", NotificationType.INFORMATION,
77 new NotificationListener() {
79 public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
80 FileEditorManagerEx.getInstanceEx(myProject).closeAllFiles();
82 ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
83 ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
84 return execCancelable(() -> {
88 }, "Updating Course", true, myProject);
89 EduUtils.synchronize();
94 notification.notify(myProject);
98 StudyUtils.registerStudyToolWindow(course, myProject);
99 ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
102 ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() {
105 if (course != null) {
106 final UISettings instance = UISettings.getInstance();
107 if (instance != null) {
108 instance.HIDE_TOOL_STRIPES = false;
109 instance.fireUISettingsChanged();
112 EduUsagesCollector.projectTypeOpened(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
120 private void registerShortcuts() {
121 StudyToolWindow window = StudyUtils.getStudyToolWindow(myProject);
122 if (window != null) {
123 List<AnAction> actionsOnToolbar = window.getActions(true);
124 if (actionsOnToolbar != null) {
125 for (AnAction action : actionsOnToolbar) {
126 if (action instanceof StudyActionWithShortcut) {
127 String id = ((StudyActionWithShortcut)action).getActionId();
128 String[] shortcuts = ((StudyActionWithShortcut)action).getShortcuts();
129 if (shortcuts != null) {
130 addShortcut(id, shortcuts);
134 addShortcut(StudyNextWindowAction.ACTION_ID, new String[]{StudyNextWindowAction.SHORTCUT, StudyNextWindowAction.SHORTCUT2});
135 addShortcut(StudyPrevWindowAction.ACTION_ID, new String[]{StudyPrevWindowAction.SHORTCUT});
138 LOG.warn("Actions on toolbar are nulls");
143 private void updateCourse() {
144 final Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse();
145 final CourseInfo info = CourseInfo.fromCourse(currentCourse);
146 if (info == null) return;
148 final File resourceDirectory = new File(currentCourse.getCourseDirectory());
149 if (resourceDirectory.exists()) {
150 FileUtil.delete(resourceDirectory);
153 final Course course = EduStepicConnector.getCourse(myProject, info);
155 if (course == null) return;
156 flushCourse(myProject, course);
157 course.initCourse(false);
159 StudyLanguageManager manager = StudyUtils.getLanguageManager(course);
160 if (manager == null) {
161 LOG.info("Study Language Manager is null for " + course.getLanguageById().getDisplayName());
165 final ArrayList<Lesson> updatedLessons = new ArrayList<>();
168 for (Lesson lesson : course.getLessons()) {
170 Lesson studentLesson = currentCourse.getLesson(lesson.getId());
171 final String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex);
173 final File lessonDir = new File(myProject.getBasePath(), lessonDirName);
174 if (!lessonDir.exists()){
175 final File fromLesson = new File(resourceDirectory, lessonDirName);
177 FileUtil.copyDir(fromLesson, lessonDir);
179 catch (IOException e) {
180 LOG.warn("Failed to copy lesson " + fromLesson.getPath());
182 lesson.setIndex(lessonIndex);
183 lesson.initLesson(currentCourse, false);
184 for (int i = 1; i <= lesson.getTaskList().size(); i++) {
185 Task task = lesson.getTaskList().get(i - 1);
188 updatedLessons.add(lesson);
191 studentLesson.setIndex(lessonIndex);
192 updatedLessons.add(studentLesson);
195 final ArrayList<Task> tasks = new ArrayList<>();
196 for (Task task : lesson.getTaskList()) {
198 final Task studentTask = studentLesson.getTask(task.getStepicId());
199 if (studentTask != null && StudyStatus.Solved.equals(studentTask.getStatus())) {
200 studentTask.setIndex(index);
201 tasks.add(studentTask);
204 task.initTask(studentLesson, false);
205 task.setIndex(index);
207 final String taskDirName = EduNames.TASK + String.valueOf(index);
208 final File toTask = new File(lessonDir, taskDirName);
210 final String taskPath = FileUtil.join(resourceDirectory.getPath(), lessonDirName, taskDirName);
211 final File taskDir = new File(taskPath);
212 if (!taskDir.exists()) return;
213 final File[] taskFiles = taskDir.listFiles();
214 if (taskFiles == null) continue;
215 for (File fromFile : taskFiles) {
216 copyFile(fromFile, new File(toTask, fromFile.getName()));
220 studentLesson.updateTaskList(tasks);
222 currentCourse.setLessons(updatedLessons);
224 final Notification notification =
225 new Notification("Update.course", "Course update", "Current course is synchronized", NotificationType.INFORMATION);
226 notification.notify(myProject);
229 private static void copyFile(@NotNull final File from, @NotNull final File to) {
232 FileUtil.copy(from, to);
234 catch (IOException e) {
235 LOG.warn("Failed to copy " + from.getName());
240 private void addShortcut(@NotNull final String actionIdString, @NotNull final String[] shortcuts) {
241 KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
242 for (Keymap keymap : keymapManager.getAllKeymaps()) {
243 List<Pair<String, String>> pairs = myDeletedShortcuts.get(keymap);
245 pairs = new ArrayList<Pair<String, String>>();
246 myDeletedShortcuts.put(keymap, pairs);
248 for (String shortcutString : shortcuts) {
249 Shortcut studyActionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcutString), null);
250 String[] actionsIds = keymap.getActionIds(studyActionShortcut);
251 for (String actionId : actionsIds) {
252 pairs.add(Pair.create(actionId, shortcutString));
253 keymap.removeShortcut(actionId, studyActionShortcut);
255 keymap.addShortcut(actionIdString, studyActionShortcut);
261 public void projectClosed() {
262 final Course course = StudyTaskManager.getInstance(myProject).getCourse();
263 if (course != null) {
264 final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
265 if (toolWindow != null) {
266 toolWindow.getContentManager().removeAllContents(false);
268 KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
269 for (Keymap keymap : keymapManager.getAllKeymaps()) {
270 List<Pair<String, String>> pairs = myDeletedShortcuts.get(keymap);
271 if (pairs != null && !pairs.isEmpty()) {
272 for (Pair<String, String> actionShortcut : pairs) {
273 keymap.addShortcut(actionShortcut.first, new KeyboardShortcut(KeyStroke.getKeyStroke(actionShortcut.second), null));
282 public void initComponent() {
283 EditorFactory.getInstance().addEditorFactoryListener(new StudyEditorFactoryListener(), myProject);
284 ActionManager.getInstance().addAnActionListener(new AnActionListener() {
286 public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
287 AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
288 for (AnAction newAction : newGroupActions) {
289 if (newAction == action) {
290 myListener = new FileCreatedByUserListener();
291 VirtualFileManager.getInstance().addVirtualFileListener(myListener);
298 public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
299 AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
300 for (AnAction newAction : newGroupActions) {
301 if (newAction == action) {
302 VirtualFileManager.getInstance().removeVirtualFileListener(myListener);
308 public void beforeEditorTyping(char c, DataContext dataContext) {
315 public void disposeComponent() {
320 public String getComponentName() {
321 return "StudyTaskManager";
324 public static StudyProjectComponent getInstance(@NotNull final Project project) {
325 final Module module = ModuleManager.getInstance(project).getModules()[0];
326 return module.getComponent(StudyProjectComponent.class);
329 private class FileCreatedByUserListener extends VirtualFileAdapter {
331 public void fileCreated(@NotNull VirtualFileEvent event) {
332 if (myProject.isDisposed()) return;
333 final VirtualFile createdFile = event.getFile();
334 final VirtualFile taskDir = createdFile.getParent();
335 final Course course = StudyTaskManager.getInstance(myProject).getCourse();
336 if (course == null || !EduNames.STUDY.equals(course.getCourseMode())) {
339 if (taskDir != null && taskDir.getName().contains(EduNames.TASK)) {
340 int taskIndex = EduUtils.getIndex(taskDir.getName(), EduNames.TASK);
341 final VirtualFile lessonDir = taskDir.getParent();
342 if (lessonDir != null && lessonDir.getName().contains(EduNames.LESSON)) {
343 int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON);
344 List<Lesson> lessons = course.getLessons();
345 if (StudyUtils.indexIsValid(lessonIndex, lessons)) {
346 final Lesson lesson = lessons.get(lessonIndex);
347 final List<Task> tasks = lesson.getTaskList();
348 if (StudyUtils.indexIsValid(taskIndex, tasks)) {
349 final Task task = tasks.get(taskIndex);
350 final TaskFile taskFile = new TaskFile();
351 taskFile.initTaskFile(task, false);
352 taskFile.setUserCreated(true);
353 final String name = createdFile.getName();
354 taskFile.name = name;
355 //TODO: put to other steps as well
356 task.getTaskFiles().put(name, taskFile);