564c68abc7a9ac8a0276a16e1bceab4546bebca2
[idea/community.git] / python / educational-core / course-creator / src / com / jetbrains / edu / coursecreator / actions / CCAddAnswerPlaceholder.java
1 package com.jetbrains.edu.coursecreator.actions;
2
3 import com.intellij.openapi.actionSystem.AnActionEvent;
4 import com.intellij.openapi.actionSystem.Presentation;
5 import com.intellij.openapi.command.undo.BasicUndoableAction;
6 import com.intellij.openapi.command.undo.UnexpectedUndoException;
7 import com.intellij.openapi.editor.Document;
8 import com.intellij.openapi.editor.Editor;
9 import com.intellij.openapi.editor.SelectionModel;
10 import com.intellij.openapi.fileEditor.FileDocumentManager;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.util.TextRange;
13 import com.intellij.openapi.util.text.StringUtil;
14 import com.intellij.psi.PsiDocumentManager;
15 import com.intellij.psi.PsiFile;
16 import com.intellij.ui.JBColor;
17 import com.intellij.util.DocumentUtil;
18 import com.jetbrains.edu.learning.StudyUtils;
19 import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter;
20 import com.jetbrains.edu.learning.core.EduNames;
21 import com.jetbrains.edu.learning.core.EduUtils;
22 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
23 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo;
24 import com.jetbrains.edu.learning.courseFormat.TaskFile;
25 import com.jetbrains.edu.learning.ui.CCCreateAnswerPlaceholderDialog;
26 import org.jetbrains.annotations.NotNull;
27
28 import java.util.List;
29
30 public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
31
32   public CCAddAnswerPlaceholder() {
33     super("Add/Delete Answer Placeholder", "Add/Delete answer placeholder");
34   }
35
36
37   private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, int start, int end) {
38     List<AnswerPlaceholder> answerPlaceholders = taskFile.getActivePlaceholders();
39     for (AnswerPlaceholder existingAnswerPlaceholder : answerPlaceholders) {
40       int twStart = existingAnswerPlaceholder.getOffset();
41       int twEnd = existingAnswerPlaceholder.getPossibleAnswerLength() + twStart;
42       if ((start >= twStart && start < twEnd) || (end > twStart && end <= twEnd) ||
43           (twStart >= start && twStart < end) || (twEnd > start && twEnd <= end)) {
44         return true;
45       }
46     }
47     return false;
48   }
49
50   private void addPlaceholder(@NotNull CCState state) {
51     Editor editor = state.getEditor();
52     Project project = state.getProject();
53     PsiFile file = state.getFile();
54
55     final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
56     if (document == null) {
57       return;
58     }
59     FileDocumentManager.getInstance().saveDocument(document);
60     final SelectionModel model = editor.getSelectionModel();
61     final int offset = model.hasSelection() ? model.getSelectionStart() : editor.getCaretModel().getOffset();
62     TaskFile taskFile = state.getTaskFile();
63     int stepIndex = state.getTaskFile().getTask().getActiveSubtaskIndex();
64
65     AnswerPlaceholder existingPlaceholder = taskFile.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders());
66     if (existingPlaceholder != null) {
67       int visibleLength = existingPlaceholder.getVisibleLength(stepIndex);
68       int placeholderOffset = existingPlaceholder.getOffset();
69       String possibleAnswer = document.getText(TextRange.create(placeholderOffset, placeholderOffset + visibleLength));
70       AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo();
71       existingPlaceholder.getSubtaskInfos().put(stepIndex, info);
72       info.setPossibleAnswer(possibleAnswer);
73       StudyUtils.drawAllWindows(editor, taskFile);
74       return;
75     }
76     final AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder();
77     AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo();
78     info.setNeedInsertText(!model.hasSelection());
79     answerPlaceholder.getSubtaskInfos().put(stepIndex, info);
80     int index = taskFile.getAnswerPlaceholders().size();
81     answerPlaceholder.setIndex(index);
82     answerPlaceholder.setTaskFile(taskFile);
83     taskFile.sortAnswerPlaceholders();
84     answerPlaceholder.setOffset(offset);
85     answerPlaceholder.setUseLength(false);
86
87     String defaultPlaceholderText = "type here";
88     CCCreateAnswerPlaceholderDialog dlg = createDialog(project, answerPlaceholder);
89     if (!dlg.showAndGet()) {
90       return;
91     }
92     String answerPlaceholderText = dlg.getTaskText();
93     answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : defaultPlaceholderText);
94     answerPlaceholder.setTaskText(StringUtil.notNullize(answerPlaceholderText));
95     answerPlaceholder.setLength(model.hasSelection() ? StringUtil.notNullize(answerPlaceholderText).length() : 0);
96     answerPlaceholder.setHints(dlg.getHints());
97
98     if (!model.hasSelection()) {
99       DocumentUtil.writeInRunUndoTransparentAction(() -> document.insertString(offset, defaultPlaceholderText));
100     }
101
102     answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : defaultPlaceholderText);
103     AddAction action = new AddAction(answerPlaceholder, taskFile, editor);
104     EduUtils.runUndoableAction(project, "Add Answer Placeholder", action);
105   }
106
107   static class AddAction extends BasicUndoableAction {
108     private final AnswerPlaceholder myPlaceholder;
109     private final TaskFile myTaskFile;
110     private final Editor myEditor;
111
112     public AddAction(AnswerPlaceholder placeholder, TaskFile taskFile, Editor editor) {
113       super(editor.getDocument());
114       myPlaceholder = placeholder;
115       myTaskFile = taskFile;
116       myEditor = editor;
117     }
118
119     @Override
120     public void undo() throws UnexpectedUndoException {
121       final List<AnswerPlaceholder> answerPlaceholders = myTaskFile.getAnswerPlaceholders();
122       if (answerPlaceholders.contains(myPlaceholder)) {
123         answerPlaceholders.remove(myPlaceholder);
124         myEditor.getMarkupModel().removeAllHighlighters();
125         StudyUtils.drawAllWindows(myEditor, myTaskFile);
126         EduAnswerPlaceholderPainter.createGuardedBlocks(myEditor, myTaskFile);
127       }
128     }
129
130     @Override
131     public void redo() throws UnexpectedUndoException {
132       myTaskFile.addAnswerPlaceholder(myPlaceholder);
133       EduAnswerPlaceholderPainter.drawAnswerPlaceholder(myEditor, myPlaceholder, JBColor.BLUE);
134       EduAnswerPlaceholderPainter.createGuardedBlocks(myEditor, myPlaceholder);
135     }
136   }
137
138   @Override
139   protected void performAnswerPlaceholderAction(@NotNull CCState state) {
140     if (canAddPlaceholder(state)) {
141       addPlaceholder(state);
142       return;
143     }
144     if (canDeletePlaceholder(state)) {
145       deletePlaceholder(state);
146     }
147   }
148
149   private static void deletePlaceholder(@NotNull CCState state) {
150     Project project = state.getProject();
151     TaskFile taskFile = state.getTaskFile();
152     AnswerPlaceholder answerPlaceholder = state.getAnswerPlaceholder();
153     EduUtils.runUndoableAction(project, "Delete Answer Placeholder", new AddAction(answerPlaceholder, taskFile, state.getEditor()) {
154       @Override
155       public void undo() throws UnexpectedUndoException {
156         super.redo();
157       }
158
159       @Override
160       public void redo() throws UnexpectedUndoException {
161         super.undo();
162       }
163     });
164   }
165
166   @Override
167   public void update(@NotNull AnActionEvent event) {
168     final Presentation presentation = event.getPresentation();
169     presentation.setEnabledAndVisible(false);
170
171     CCState state = getState(event);
172     if (state == null) {
173       return;
174     }
175
176     presentation.setVisible(true);
177     if (canAddPlaceholder(state) || canDeletePlaceholder(state)) {
178       presentation.setEnabled(true);
179       presentation.setText((state.getAnswerPlaceholder() == null ? "Add " : "Delete ") + EduNames.ANSWER_PLACEHOLDER);
180     }
181   }
182
183
184   private static boolean canAddPlaceholder(@NotNull CCState state) {
185     Editor editor = state.getEditor();
186     SelectionModel selectionModel = editor.getSelectionModel();
187     if (selectionModel.hasSelection()) {
188       int start = selectionModel.getSelectionStart();
189       int end = selectionModel.getSelectionEnd();
190       return !arePlaceholdersIntersect(state.getTaskFile(), start, end);
191     }
192     int offset = editor.getCaretModel().getOffset();
193     return state.getTaskFile().getAnswerPlaceholder(offset) == null;
194   }
195
196   private static boolean canDeletePlaceholder(@NotNull CCState state) {
197     if (state.getEditor().getSelectionModel().hasSelection()) {
198       return false;
199     }
200     return state.getAnswerPlaceholder() != null;
201   }
202
203   protected CCCreateAnswerPlaceholderDialog createDialog(Project project, AnswerPlaceholder answerPlaceholder) {
204     return new CCCreateAnswerPlaceholderDialog(project, StringUtil.notNullize(answerPlaceholder.getTaskText()),
205                                                answerPlaceholder.getHints());
206   }
207 }