delete subtask
[idea/community.git] / python / educational-core / course-creator / src / com / jetbrains / edu / coursecreator / CCSubtaskEditorNotificationProvider.java
1 package com.jetbrains.edu.coursecreator;
2
3 import com.intellij.openapi.application.ApplicationManager;
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.editor.colors.EditorColors;
6 import com.intellij.openapi.editor.colors.EditorColorsManager;
7 import com.intellij.openapi.fileEditor.FileEditor;
8 import com.intellij.openapi.fileEditor.FileEditorManager;
9 import com.intellij.openapi.project.DumbAware;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.ui.popup.JBPopupFactory;
12 import com.intellij.openapi.ui.popup.ListSeparator;
13 import com.intellij.openapi.ui.popup.PopupStep;
14 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
15 import com.intellij.openapi.util.Key;
16 import com.intellij.openapi.util.io.FileUtil;
17 import com.intellij.openapi.util.io.FileUtilRt;
18 import com.intellij.openapi.vfs.VirtualFile;
19 import com.intellij.ui.EditorNotificationPanel;
20 import com.intellij.ui.EditorNotifications;
21 import com.intellij.ui.awt.RelativePoint;
22 import com.intellij.util.containers.ContainerUtil;
23 import com.jetbrains.edu.coursecreator.actions.CCNewSubtaskAction;
24 import com.jetbrains.edu.learning.StudySubtaskUtils;
25 import com.jetbrains.edu.learning.StudyUtils;
26 import com.jetbrains.edu.learning.core.EduNames;
27 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
28 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo;
29 import com.jetbrains.edu.learning.courseFormat.Task;
30 import com.jetbrains.edu.learning.courseFormat.TaskFile;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import java.awt.*;
35 import java.io.IOException;
36 import java.util.*;
37 import java.util.List;
38
39 public class CCSubtaskEditorNotificationProvider extends EditorNotifications.Provider<EditorNotificationPanel> implements DumbAware {
40   private static final Key<EditorNotificationPanel> KEY = Key.create("edu.coursecreator.subtask");
41   public static final String SWITCH_SUBTASK = "Switch subtask";
42   public static final Integer ADD_SUBTASK_ID = -1;
43   private static final Logger LOG = Logger.getInstance(CCSubtaskEditorNotificationProvider.class);
44   private final Project myProject;
45
46   public CCSubtaskEditorNotificationProvider(Project project) {
47     myProject = project;
48   }
49
50   @NotNull
51   @Override
52   public Key<EditorNotificationPanel> getKey() {
53     return KEY;
54   }
55
56   @Nullable
57   @Override
58   public EditorNotificationPanel createNotificationPanel(@NotNull VirtualFile file, @NotNull FileEditor fileEditor) {
59     if (!CCUtils.isCourseCreator(myProject)) {
60       return null;
61     }
62     boolean isTestFile = CCUtils.isTestsFile(myProject, file);
63     if (!isTestFile && StudyUtils.getTaskFile(myProject, file) == null) {
64       return null;
65     }
66     Task task = StudyUtils.getTaskForFile(myProject, file);
67     if (task == null || !task.hasSubtasks()) {
68       return null;
69     }
70     EditorNotificationPanel panel = new EditorNotificationPanel() {
71       @Override
72       public Color getBackground() {
73         Color color = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.GUTTER_BACKGROUND);
74         return color == null ? super.getBackground() : color;
75       }
76     };
77     String header = isTestFile ? "test" : "task file";
78     int activeSubtaskIndex = task.getActiveSubtaskIndex() + 1;
79     int subtaskSize = task.getLastSubtaskIndex() + 1;
80     panel.setText("This is " + header + " for " + EduNames.SUBTASK + " " + activeSubtaskIndex + "/" + subtaskSize);
81     panel.createActionLabel(SWITCH_SUBTASK, () -> {
82       ArrayList<Integer> values = new ArrayList<>();
83       for (int i = 0; i <= task.getLastSubtaskIndex(); i++) {
84         values.add(i);
85       }
86       values.add(ADD_SUBTASK_ID);
87       JBPopupFactory.getInstance().createListPopup(new SwitchSubtaskPopupStep(SWITCH_SUBTASK, values, task, file))
88         .show(RelativePoint.getSouthEastOf(panel));
89     });
90     return panel;
91   }
92
93   private class SwitchSubtaskPopupStep extends BaseListPopupStep<Integer> {
94     private final Task myTask;
95     private final VirtualFile myFile;
96
97     public SwitchSubtaskPopupStep(@Nullable String title,
98                                   List<Integer> values,
99                                   Task task, VirtualFile file) {
100       super(title, values);
101       myTask = task;
102       myFile = file;
103     }
104
105     @NotNull
106     @Override
107     public String getTextFor(Integer value) {
108       if (value.equals(ADD_SUBTASK_ID)) {
109         return CCNewSubtaskAction.NEW_SUBTASK;
110       }
111       int subtaskNum = value + 1;
112       String text = EduNames.SUBTASK + " " + subtaskNum;
113       if (value == myTask.getActiveSubtaskIndex()) {
114         text += " (selected)";
115       }
116       return text;
117     }
118
119     @Override
120     public PopupStep onChosen(Integer selectedValue, boolean finalChoice) {
121       if (finalChoice) {
122         if (selectedValue.equals(ADD_SUBTASK_ID)) {
123           return doFinalStep(() -> CCNewSubtaskAction.addSubtask(myFile, myProject));
124         }
125         StudySubtaskUtils.switchStep(myProject, myTask, selectedValue);
126       }
127       else {
128         if (hasSubstep(selectedValue)) {
129           return new ActionsPopupStep(myTask, selectedValue);
130         }
131       }
132       return super.onChosen(selectedValue, false);
133     }
134
135     @Override
136     public boolean hasSubstep(Integer selectedValue) {
137       return !selectedValue.equals(ADD_SUBTASK_ID);
138     }
139
140     @Override
141     public int getDefaultOptionIndex() {
142       return myTask.getActiveSubtaskIndex();
143     }
144
145     @Nullable
146     @Override
147     public ListSeparator getSeparatorAbove(Integer value) {
148       return value.equals(ADD_SUBTASK_ID) ? new ListSeparator() : null;
149     }
150   }
151
152   private class ActionsPopupStep extends BaseListPopupStep<String> {
153
154     public static final String SELECT = "Select";
155     public static final String DELETE = "Delete";
156     private final Task myTask;
157     private final int mySubtaskIndex;
158
159     public ActionsPopupStep(Task task, int subtaskIndex) {
160       super(null, Arrays.asList(SELECT, DELETE));
161       myTask = task;
162       mySubtaskIndex = subtaskIndex;
163     }
164
165     @Override
166     public PopupStep onChosen(String selectedValue, boolean finalChoice) {
167       if (finalChoice) {
168         if (selectedValue.equals(SELECT)) {
169           StudySubtaskUtils.switchStep(myProject, myTask, mySubtaskIndex);
170         }
171         else {
172           for (TaskFile taskFile : myTask.getTaskFiles().values()) {
173             List<AnswerPlaceholder> emptyPlaceholders = new ArrayList<>();
174             for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
175               Map<Integer, AnswerPlaceholderSubtaskInfo> infos = placeholder.getSubtaskInfos();
176               if (infos.containsKey(mySubtaskIndex)) {
177                 infos.remove(mySubtaskIndex);
178                 if (infos.isEmpty()) {
179                   emptyPlaceholders.add(placeholder);
180                 }
181               }
182             }
183             taskFile.getAnswerPlaceholders().removeAll(emptyPlaceholders);
184           }
185           VirtualFile taskDir = myTask.getTaskDir(myProject);
186           if (taskDir == null) {
187             return FINAL_CHOICE;
188           }
189           deleteSubtaskFiles(taskDir);
190           if (mySubtaskIndex != myTask.getLastSubtaskIndex()) {
191             renameFiles(taskDir);
192             updateInfoIndexes();
193           }
194           myTask.setLastSubtaskIndex(myTask.getLastSubtaskIndex() - 1);
195           int activeSubtaskIndex = myTask.getActiveSubtaskIndex();
196           if (mySubtaskIndex != 0 && activeSubtaskIndex == mySubtaskIndex) {
197             StudySubtaskUtils.switchStep(myProject, myTask, mySubtaskIndex - 1);
198           }
199           if (activeSubtaskIndex > mySubtaskIndex) {
200             myTask.setActiveSubtaskIndex(activeSubtaskIndex - 1);
201           }
202           StudySubtaskUtils.updateUI(myProject, myTask, taskDir);
203           for (VirtualFile file : FileEditorManager.getInstance(myProject).getOpenFiles()) {
204             EditorNotifications.getInstance(myProject).updateNotifications(file);
205           }
206
207           return FINAL_CHOICE;
208         }
209       }
210       return super.onChosen(selectedValue, finalChoice);
211     }
212
213     private void updateInfoIndexes() {
214       for (TaskFile taskFile : myTask.getTaskFiles().values()) {
215         for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
216           List<Integer> filtered = ContainerUtil.filter(placeholder.getSubtaskInfos().keySet(), index -> index > mySubtaskIndex);
217           Map<Integer, AnswerPlaceholderSubtaskInfo> savedInfos = new HashMap<>();
218           for (Integer index : filtered) {
219             savedInfos.put(index, placeholder.getSubtaskInfos().get(index));
220             placeholder.getSubtaskInfos().remove(index);
221           }
222           for (Integer index : filtered) {
223             placeholder.getSubtaskInfos().put(index - 1, savedInfos.get(index));
224           }
225         }
226       }
227     }
228
229     private void renameFiles(VirtualFile taskDir) {
230       ApplicationManager.getApplication().runWriteAction(() -> {
231         Map<VirtualFile, String> newNames = new HashMap<>();
232         for (VirtualFile virtualFile : taskDir.getChildren()) {
233           int subtaskIndex = CCUtils.getSubtaskIndex(myProject, virtualFile);
234           if (subtaskIndex == -1) {
235             continue;
236           }
237           if (subtaskIndex > mySubtaskIndex) {
238             String index = subtaskIndex == 1 ? "" : Integer.toString(subtaskIndex - 1);
239             String fileName = virtualFile.getName();
240             String nameWithoutExtension = FileUtil.getNameWithoutExtension(fileName);
241             String extension = FileUtilRt.getExtension(fileName);
242             int subtaskMarkerIndex = nameWithoutExtension.indexOf(EduNames.SUBTASK_MARKER);
243             String newName = subtaskMarkerIndex == -1
244                              ? nameWithoutExtension
245                              : nameWithoutExtension.substring(0, subtaskMarkerIndex);
246             newName += index.isEmpty() ? "" : EduNames.SUBTASK_MARKER;
247             newName += index + "." + extension;
248             newNames.put(virtualFile, newName);
249           }
250         }
251         for (Map.Entry<VirtualFile, String> entry : newNames.entrySet()) {
252           try {
253             entry.getKey().rename(myProject, entry.getValue());
254           }
255           catch (IOException e) {
256             LOG.info(e);
257           }
258         }
259       });
260     }
261
262     private void deleteSubtaskFiles(VirtualFile taskDir) {
263       ApplicationManager.getApplication().runWriteAction(() -> {
264         List<VirtualFile> filesToDelete = new ArrayList<>();
265         for (VirtualFile file : taskDir.getChildren()) {
266           int index = CCUtils.getSubtaskIndex(myProject, file);
267           if (index != -1 && mySubtaskIndex == index) {
268             filesToDelete.add(file);
269           }
270         }
271         for (VirtualFile file : filesToDelete) {
272           try {
273             file.delete(myProject);
274           }
275           catch (IOException e) {
276             LOG.info(e);
277           }
278         }
279       });
280     }
281   }
282 }