18deb4d89a940fb98793018708bb62da35396dac
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / StudyProjectComponent.java
1 package com.jetbrains.edu.learning;
2
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.ToolWindowAnchor;
29 import com.intellij.openapi.wm.ToolWindowManager;
30 import com.intellij.util.containers.hash.HashMap;
31 import com.jetbrains.edu.learning.actions.StudyActionWithShortcut;
32 import com.jetbrains.edu.learning.actions.StudyNextWindowAction;
33 import com.jetbrains.edu.learning.actions.StudyPrevWindowAction;
34 import com.jetbrains.edu.learning.core.EduNames;
35 import com.jetbrains.edu.learning.core.EduUtils;
36 import com.jetbrains.edu.learning.courseFormat.*;
37 import com.jetbrains.edu.learning.editor.StudyEditorFactoryListener;
38 import com.jetbrains.edu.learning.statistics.EduUsagesCollector;
39 import com.jetbrains.edu.learning.stepic.CourseInfo;
40 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
41 import com.jetbrains.edu.learning.ui.StudyToolWindow;
42 import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
43 import javafx.application.Platform;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import javax.swing.*;
48 import javax.swing.event.HyperlinkEvent;
49 import java.io.File;
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.Map;
54
55 import static com.jetbrains.edu.learning.StudyUtils.execCancelable;
56 import static com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator.flushCourse;
57
58
59 public class StudyProjectComponent implements ProjectComponent {
60   private static final Logger LOG = Logger.getInstance(StudyProjectComponent.class.getName());
61   private final Project myProject;
62   private FileCreatedByUserListener myListener;
63   private Map<Keymap, List<Pair<String, String>>> myDeletedShortcuts = new HashMap<Keymap, List<Pair<String, String>>>();
64   private StudyProjectComponent(@NotNull final Project project) {
65     myProject = project;
66   }
67
68   @Override
69   public void projectOpened() {
70     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
71     // Check if user has javafx lib in his JDK. Now bundled JDK doesn't have this lib inside.
72     if (StudyUtils.hasJavaFx()) {
73       Platform.setImplicitExit(false);
74     }
75
76     if (course != null && !course.isUpToDate()) {
77       final Notification notification =
78         new Notification("Update.course", "Course Updates", "Course is ready to <a href=\"update\">update</a>", NotificationType.INFORMATION,
79                          new NotificationListener() {
80                            @Override
81                            public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
82                              FileEditorManagerEx.getInstanceEx(myProject).closeAllFiles();
83
84                              ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
85                                ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
86                                return execCancelable(() -> {
87                                  updateCourse();
88                                  return true;
89                                });
90                              }, "Updating Course", true, myProject);
91                              EduUtils.synchronize();
92                              course.setUpdated();
93
94                            }
95                          });
96       notification.notify(myProject);
97
98     }
99
100     registerStudyToolWindow(course);
101     ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
102       @Override
103       public void run() {
104         ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() {
105           @Override
106           public void run() {
107             if (course != null) {
108               final UISettings instance = UISettings.getInstance();
109               if (instance != null) {
110                 instance.HIDE_TOOL_STRIPES = false;
111                 instance.fireUISettingsChanged();
112               }
113               registerShortcuts();
114               EduUsagesCollector.projectTypeOpened(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY);
115             }
116           }
117         });
118       }
119     });
120   }
121
122   public void registerStudyToolWindow(@Nullable final Course course) {
123     if (course != null && "PyCharm".equals(course.getCourseType())) {
124       final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
125       registerToolWindows(toolWindowManager);
126       final ToolWindow studyToolWindow = toolWindowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
127       if (studyToolWindow != null) {
128         studyToolWindow.show(null);
129         StudyUtils.initToolWindows(myProject);
130       }
131     }
132   }
133
134   private void registerShortcuts() {
135     StudyToolWindow window = StudyUtils.getStudyToolWindow(myProject);
136     if (window != null) {
137       List<AnAction> actionsOnToolbar = window.getActions(true);
138       if (actionsOnToolbar != null) {
139         for (AnAction action : actionsOnToolbar) {
140           if (action instanceof StudyActionWithShortcut) {
141             String id = ((StudyActionWithShortcut)action).getActionId();
142             String[] shortcuts = ((StudyActionWithShortcut)action).getShortcuts();
143             if (shortcuts != null) {
144               addShortcut(id, shortcuts);
145             }
146           }
147         }
148         addShortcut(StudyNextWindowAction.ACTION_ID, new String[]{StudyNextWindowAction.SHORTCUT, StudyNextWindowAction.SHORTCUT2});
149         addShortcut(StudyPrevWindowAction.ACTION_ID, new String[]{StudyPrevWindowAction.SHORTCUT});
150       }
151       else {
152         LOG.warn("Actions on toolbar are nulls");
153       }
154     }
155   }
156
157   private void registerToolWindows(@NotNull final ToolWindowManager toolWindowManager) {
158     final ToolWindow toolWindow = toolWindowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
159     if (toolWindow == null) {
160       toolWindowManager.registerToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW, true, ToolWindowAnchor.RIGHT, myProject, true);
161     }
162   }
163
164   private void updateCourse() {
165     final Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse();
166     final CourseInfo info = CourseInfo.fromCourse(currentCourse);
167     if (info == null) return;
168
169     final File resourceDirectory = new File(currentCourse.getCourseDirectory());
170     if (resourceDirectory.exists()) {
171       FileUtil.delete(resourceDirectory);
172     }
173
174     final Course course = EduStepicConnector.getCourse(myProject, info);
175
176     if (course == null) return;
177     flushCourse(myProject, course);
178     course.initCourse(false);
179
180     StudyLanguageManager manager = StudyUtils.getLanguageManager(course);
181     if (manager == null) {
182       LOG.info("Study Language Manager is null for " + course.getLanguageById().getDisplayName());
183       return;
184     }
185
186     final ArrayList<Lesson> updatedLessons = new ArrayList<>();
187
188     int lessonIndex = 0;
189     for (Lesson lesson : course.getLessons()) {
190       lessonIndex += 1;
191       Lesson studentLesson = currentCourse.getLesson(lesson.getId());
192       final String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex);
193
194       final File lessonDir = new File(myProject.getBasePath(), lessonDirName);
195       if (!lessonDir.exists()){
196         final File fromLesson = new File(resourceDirectory, lessonDirName);
197         try {
198           FileUtil.copyDir(fromLesson, lessonDir);
199         }
200         catch (IOException e) {
201           LOG.warn("Failed to copy lesson " + fromLesson.getPath());
202         }
203         lesson.setIndex(lessonIndex);
204         lesson.initLesson(currentCourse, false);
205         for (int i = 1; i <= lesson.getTaskList().size(); i++) {
206           Task task = lesson.getTaskList().get(i - 1);
207           task.setIndex(i);
208         }
209         updatedLessons.add(lesson);
210         continue;
211       }
212       studentLesson.setIndex(lessonIndex);
213       updatedLessons.add(studentLesson);
214
215       int index = 0;
216       final ArrayList<Task> tasks = new ArrayList<>();
217       for (Task task : lesson.getTaskList()) {
218         index += 1;
219         final Task studentTask = studentLesson.getTask(task.getStepicId());
220         if (studentTask != null && StudyStatus.Solved.equals(studentTask.getStatus())) {
221           studentTask.setIndex(index);
222           tasks.add(studentTask);
223           continue;
224         }
225         task.initTask(studentLesson, false);
226         task.setIndex(index);
227
228         final String taskDirName = EduNames.TASK + String.valueOf(index);
229         final File toTask = new File(lessonDir, taskDirName);
230
231         final String taskPath = FileUtil.join(resourceDirectory.getPath(), lessonDirName, taskDirName);
232         final File taskDir = new File(taskPath);
233         if (!taskDir.exists()) return;
234         final File[] taskFiles = taskDir.listFiles();
235         if (taskFiles == null) continue;
236         for (File fromFile : taskFiles) {
237           copyFile(fromFile, new File(toTask, fromFile.getName()));
238         }
239         tasks.add(task);
240       }
241       studentLesson.updateTaskList(tasks);
242     }
243     currentCourse.setLessons(updatedLessons);
244
245     final Notification notification =
246       new Notification("Update.course", "Course update", "Current course is synchronized", NotificationType.INFORMATION);
247     notification.notify(myProject);
248   }
249
250   private static void copyFile(@NotNull final File from, @NotNull final File to) {
251     if (from.exists()) {
252       try {
253         FileUtil.copy(from, to);
254       }
255       catch (IOException e) {
256         LOG.warn("Failed to copy " + from.getName());
257       }
258     }
259   }
260
261   private void addShortcut(@NotNull final String actionIdString, @NotNull final String[] shortcuts) {
262     KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
263     for (Keymap keymap : keymapManager.getAllKeymaps()) {
264       List<Pair<String, String>> pairs = myDeletedShortcuts.get(keymap);
265       if (pairs == null) {
266         pairs = new ArrayList<Pair<String, String>>();
267         myDeletedShortcuts.put(keymap, pairs);
268       }
269       for (String shortcutString : shortcuts) {
270         Shortcut studyActionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcutString), null);
271         String[] actionsIds = keymap.getActionIds(studyActionShortcut);
272         for (String actionId : actionsIds) {
273           pairs.add(Pair.create(actionId, shortcutString));
274           keymap.removeShortcut(actionId, studyActionShortcut);
275         }
276         keymap.addShortcut(actionIdString, studyActionShortcut);
277       }
278     }
279   }
280
281   @Override
282   public void projectClosed() {
283     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
284     if (course != null) {
285       final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
286       if (toolWindow != null) {
287         toolWindow.getContentManager().removeAllContents(false);
288       }
289       KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
290       for (Keymap keymap : keymapManager.getAllKeymaps()) {
291         List<Pair<String, String>> pairs = myDeletedShortcuts.get(keymap);
292         if (pairs != null && !pairs.isEmpty()) {
293           for (Pair<String, String> actionShortcut : pairs) {
294             keymap.addShortcut(actionShortcut.first, new KeyboardShortcut(KeyStroke.getKeyStroke(actionShortcut.second), null));
295           }
296         }
297       }
298     }
299     myListener = null;
300   }
301
302   @Override
303   public void initComponent() {
304     EditorFactory.getInstance().addEditorFactoryListener(new StudyEditorFactoryListener(), myProject);
305     ActionManager.getInstance().addAnActionListener(new AnActionListener() {
306       @Override
307       public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
308         AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
309         for (AnAction newAction : newGroupActions) {
310           if (newAction == action) {
311             myListener =  new FileCreatedByUserListener();
312             VirtualFileManager.getInstance().addVirtualFileListener(myListener);
313             break;
314           }
315         }
316       }
317
318       @Override
319       public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
320         AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
321         for (AnAction newAction : newGroupActions) {
322           if (newAction == action) {
323             VirtualFileManager.getInstance().removeVirtualFileListener(myListener);
324           }
325         }
326       }
327
328       @Override
329       public void beforeEditorTyping(char c, DataContext dataContext) {
330
331       }
332     });
333   }
334
335   @Override
336   public void disposeComponent() {
337   }
338
339   @NotNull
340   @Override
341   public String getComponentName() {
342     return "StudyTaskManager";
343   }
344
345   public static StudyProjectComponent getInstance(@NotNull final Project project) {
346     final Module module = ModuleManager.getInstance(project).getModules()[0];
347     return module.getComponent(StudyProjectComponent.class);
348   }
349
350   private class FileCreatedByUserListener extends VirtualFileAdapter {
351     @Override
352     public void fileCreated(@NotNull VirtualFileEvent event) {
353       if (myProject.isDisposed()) return;
354       final VirtualFile createdFile = event.getFile();
355       final VirtualFile taskDir = createdFile.getParent();
356       final Course course = StudyTaskManager.getInstance(myProject).getCourse();
357       if (course == null || !EduNames.STUDY.equals(course.getCourseMode())) {
358         return;
359       }
360       if (taskDir != null && taskDir.getName().contains(EduNames.TASK)) {
361         int taskIndex = EduUtils.getIndex(taskDir.getName(), EduNames.TASK);
362         final VirtualFile lessonDir = taskDir.getParent();
363         if (lessonDir != null && lessonDir.getName().contains(EduNames.LESSON)) {
364           int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON);
365           List<Lesson> lessons = course.getLessons();
366           if (StudyUtils.indexIsValid(lessonIndex, lessons)) {
367             final Lesson lesson = lessons.get(lessonIndex);
368             final List<Task> tasks = lesson.getTaskList();
369             if (StudyUtils.indexIsValid(taskIndex, tasks)) {
370               final Task task = tasks.get(taskIndex);
371               final TaskFile taskFile = new TaskFile();
372               taskFile.initTaskFile(task, false);
373               taskFile.setUserCreated(true);
374               final String name = createdFile.getName();
375               taskFile.name = name;
376               //TODO: put to other steps as well
377               task.getTaskFiles().put(name, taskFile);
378             }
379           }
380         }
381       }
382     }
383   }
384 }