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