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