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