EDU-501 NPE on Startup
[idea/community.git] / python / educational / interactive-learning / 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.NotificationType;
6 import com.intellij.openapi.actionSystem.*;
7 import com.intellij.openapi.actionSystem.ex.AnActionListener;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.components.ProjectComponent;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.editor.EditorFactory;
12 import com.intellij.openapi.keymap.Keymap;
13 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
14 import com.intellij.openapi.module.Module;
15 import com.intellij.openapi.module.ModuleManager;
16 import com.intellij.openapi.project.DumbAwareRunnable;
17 import com.intellij.openapi.project.Project;
18 import com.intellij.openapi.util.Pair;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.vfs.VirtualFile;
21 import com.intellij.openapi.vfs.VirtualFileAdapter;
22 import com.intellij.openapi.vfs.VirtualFileEvent;
23 import com.intellij.openapi.vfs.VirtualFileManager;
24 import com.intellij.openapi.wm.ToolWindow;
25 import com.intellij.openapi.wm.ToolWindowAnchor;
26 import com.intellij.openapi.wm.ToolWindowManager;
27 import com.intellij.util.containers.hash.HashMap;
28 import com.jetbrains.edu.EduNames;
29 import com.jetbrains.edu.EduUtils;
30 import com.jetbrains.edu.courseFormat.Course;
31 import com.jetbrains.edu.courseFormat.Lesson;
32 import com.jetbrains.edu.courseFormat.Task;
33 import com.jetbrains.edu.courseFormat.TaskFile;
34 import com.jetbrains.edu.learning.actions.*;
35 import com.jetbrains.edu.learning.editor.StudyEditorFactoryListener;
36 import com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory;
37 import com.jetbrains.edu.learning.ui.StudyToolWindowFactory;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40
41 import javax.swing.*;
42 import java.io.File;
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Map;
47
48
49 public class StudyProjectComponent implements ProjectComponent {
50   private static final Logger LOG = Logger.getInstance(StudyProjectComponent.class.getName());
51   private final Project myProject;
52
53   private FileCreatedByUserListener myListener;
54
55   private Map<Keymap, List<Pair<String, String>>> myDeletedShortcuts = new HashMap<Keymap, List<Pair<String, String>>>();
56   private StudyProjectComponent(@NotNull final Project project) {
57     myProject = project;
58   }
59
60   @Override
61   public void projectOpened() {
62     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
63     if (course != null && !course.isUpToDate()) {
64       course.setUpToDate(true);
65       updateCourse();
66     }
67
68     registerStudyToolwindow(course);
69     ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
70       @Override
71       public void run() {
72         ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() {
73           @Override
74           public void run() {
75             if (course != null) {
76               UISettings.getInstance().HIDE_TOOL_STRIPES = false;
77               UISettings.getInstance().fireUISettingsChanged();
78               registerShortcuts();
79             }
80           }
81         });
82       }
83     });
84   }
85
86   public void registerStudyToolwindow(@Nullable final Course course) {
87     if (course != null && "PyCharm".equals(course.getCourseType())) {
88       final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
89       registerToolWindows(toolWindowManager);
90       final ToolWindow studyToolWindow = toolWindowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
91       final ToolWindow progressToolWindow = toolWindowManager.getToolWindow(StudyProgressToolWindowFactory.ID);
92       if (studyToolWindow != null) {
93         studyToolWindow.show(null);
94       }
95       if (progressToolWindow != null) {
96         StudyUtils.updateToolWindows(myProject);
97         progressToolWindow.show(null);
98       }
99     }
100   }
101
102   private void registerShortcuts() {
103     addShortcut(StudyNextWindowAction.ACTION_ID, new String[]{StudyNextWindowAction.SHORTCUT, StudyNextWindowAction.SHORTCUT2});
104     addShortcut(StudyPrevWindowAction.ACTION_ID, new String[]{StudyPrevWindowAction.SHORTCUT});
105     addShortcut(StudyShowHintAction.ACTION_ID, new String[]{StudyShowHintAction.SHORTCUT});
106     addShortcut(StudyCheckAction.ACTION_ID, new String[]{StudyCheckAction.SHORTCUT});
107     addShortcut(StudyNextStudyTaskAction.ACTION_ID, new String[]{StudyNextStudyTaskAction.SHORTCUT});
108     addShortcut(StudyPreviousStudyTaskAction.ACTION_ID, new String[]{StudyPreviousStudyTaskAction.SHORTCUT});
109     addShortcut(StudyRefreshTaskFileAction.ACTION_ID, new String[]{StudyRefreshTaskFileAction.SHORTCUT});
110   }
111
112   private void registerToolWindows(@NotNull final ToolWindowManager toolWindowManager) {
113     final ToolWindow toolWindow = toolWindowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
114     if (toolWindow == null) {
115       toolWindowManager.registerToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW, true, ToolWindowAnchor.RIGHT, myProject, true);
116     }
117     ToolWindow progressToolWindow = toolWindowManager.getToolWindow(StudyProgressToolWindowFactory.ID);
118     if (progressToolWindow == null) {
119       toolWindowManager.registerToolWindow(StudyProgressToolWindowFactory.ID, true, ToolWindowAnchor.LEFT, myProject, true, true);
120     }
121   }
122
123   private void updateCourse() {
124     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
125     if (course == null) {
126       return;
127     }
128     final File resourceDirectory = new File(course.getCourseDirectory());
129     if (!resourceDirectory.exists()) {
130       return;
131     }
132     StudyLanguageManager manager = StudyUtils.getLanguageManager(course);
133     if (manager == null) {
134       LOG.info("Study Language Manager is null for " + course.getLanguageById().getDisplayName());
135       return;
136     }
137     final File[] files = resourceDirectory.listFiles();
138     if (files == null) return;
139     for (File file : files) {
140       String testHelper = manager.getTestHelperFileName();
141       if (file.getName().equals(testHelper)) {
142         copyFile(file, new File(myProject.getBasePath(), testHelper));
143       }
144       if (file.getName().startsWith(EduNames.LESSON)) {
145         final File[] tasks = file.listFiles();
146         if (tasks == null) continue;
147         for (File task : tasks) {
148           final File taskDescr = new File(task, EduNames.TASK_HTML);
149           String testFileName = manager.getTestFileName();
150           final File taskTests = new File(task, testFileName);
151           copyFile(taskDescr, new File(new File(new File(myProject.getBasePath(), file.getName()), task.getName()), EduNames.TASK_HTML));
152           copyFile(taskTests, new File(new File(new File(myProject.getBasePath(), file.getName()), task.getName()),
153                                        testFileName));
154         }
155       }
156     }
157
158     final Notification notification =
159       new Notification("Update.course", "Course update", "Current course is synchronized", NotificationType.INFORMATION);
160     notification.notify(myProject);
161   }
162
163   private static void copyFile(@NotNull final File from, @NotNull final File to) {
164     if (from.exists()) {
165       try {
166         FileUtil.copy(from, to);
167       }
168       catch (IOException e) {
169         LOG.warn("Failed to copy " + from.getName());
170       }
171     }
172   }
173
174   private void addShortcut(@NotNull final String actionIdString, @NotNull final String[] shortcuts) {
175     KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
176     for (Keymap keymap : keymapManager.getAllKeymaps()) {
177       List<Pair<String, String>> pairs = myDeletedShortcuts.get(keymap);
178       if (pairs == null) {
179         pairs = new ArrayList<Pair<String, String>>();
180         myDeletedShortcuts.put(keymap, pairs);
181       }
182       for (String shortcutString : shortcuts) {
183         Shortcut studyActionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcutString), null);
184         String[] actionsIds = keymap.getActionIds(studyActionShortcut);
185         for (String actionId : actionsIds) {
186           pairs.add(Pair.create(actionId, shortcutString));
187           keymap.removeShortcut(actionId, studyActionShortcut);
188         }
189         keymap.addShortcut(actionIdString, studyActionShortcut);
190       }
191     }
192   }
193
194   @Override
195   public void projectClosed() {
196     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
197     if (course != null) {
198       final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW);
199       if (toolWindow != null) {
200         toolWindow.getContentManager().removeAllContents(false);
201       }
202       final ToolWindow progressToolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(StudyProgressToolWindowFactory.ID);
203       if (progressToolWindow != null) {
204         progressToolWindow.getContentManager().removeAllContents(false);
205       }
206       KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
207       for (Keymap keymap : keymapManager.getAllKeymaps()) {
208         List<Pair<String, String>> pairs = myDeletedShortcuts.get(keymap);
209         if (pairs != null && !pairs.isEmpty()) {
210           for (Pair<String, String> actionShortcut : pairs) {
211             keymap.addShortcut(actionShortcut.first, new KeyboardShortcut(KeyStroke.getKeyStroke(actionShortcut.second), null));
212           }
213         }
214        }
215     }
216     myListener = null;
217   }
218
219   @Override
220   public void initComponent() {
221     EditorFactory.getInstance().addEditorFactoryListener(new StudyEditorFactoryListener(), myProject);
222     ActionManager.getInstance().addAnActionListener(new AnActionListener() {
223       @Override
224       public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
225         AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
226         for (AnAction newAction : newGroupActions) {
227           if (newAction == action) {
228             myListener =  new FileCreatedByUserListener();
229             VirtualFileManager.getInstance().addVirtualFileListener(myListener);
230             break;
231           }
232         }
233       }
234
235       @Override
236       public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
237         AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
238         for (AnAction newAction : newGroupActions) {
239           if (newAction == action) {
240             VirtualFileManager.getInstance().removeVirtualFileListener(myListener);
241           }
242         }
243       }
244
245       @Override
246       public void beforeEditorTyping(char c, DataContext dataContext) {
247
248       }
249     });
250   }
251
252   @Override
253   public void disposeComponent() {
254   }
255
256   @NotNull
257   @Override
258   public String getComponentName() {
259     return "StudyTaskManager";
260   }
261
262   public static StudyProjectComponent getInstance(@NotNull final Project project) {
263     final Module module = ModuleManager.getInstance(project).getModules()[0];
264     return module.getComponent(StudyProjectComponent.class);
265   }
266
267   private class FileCreatedByUserListener extends VirtualFileAdapter {
268     @Override
269     public void fileCreated(@NotNull VirtualFileEvent event) {
270       if (myProject.isDisposed()) return;
271       final VirtualFile createdFile = event.getFile();
272       final VirtualFile taskDir = createdFile.getParent();
273       final Course course = StudyTaskManager.getInstance(myProject).getCourse();
274       if (taskDir != null && taskDir.getName().contains(EduNames.TASK)) {
275         int taskIndex = EduUtils.getIndex(taskDir.getName(), EduNames.TASK);
276         final VirtualFile lessonDir = taskDir.getParent();
277         if (lessonDir != null && lessonDir.getName().contains(EduNames.LESSON)) {
278           int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON);
279           if (course != null) {
280             List<Lesson> lessons = course.getLessons();
281             if (StudyUtils.indexIsValid(lessonIndex, lessons)) {
282               final Lesson lesson = lessons.get(lessonIndex);
283               final List<Task> tasks = lesson.getTaskList();
284               if (StudyUtils.indexIsValid(taskIndex, tasks)) {
285                 final Task task = tasks.get(taskIndex);
286                 final TaskFile taskFile = new TaskFile();
287                 taskFile.initTaskFile(task, false);
288                 taskFile.setUserCreated(true);
289                 final String name = createdFile.getName();
290                 taskFile.name = name;
291                 task.getTaskFiles().put(name, taskFile);
292               }
293             }
294           }
295         }
296       }
297     }
298   }
299
300 }