summaryrefslogtreecommitdiff
path: root/python/edu/course-creator/src/org/jetbrains/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'python/edu/course-creator/src/org/jetbrains/plugins')
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java80
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java58
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectGenerator.java101
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java138
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java71
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java105
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java198
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateLesson.java89
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java122
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java110
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/DeleteTaskWindow.java62
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/ShowTaskWindowText.java44
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java55
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java45
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java41
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java111
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java133
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/CCTaskLineMarkerProvider.java75
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/TaskTextGutter.java60
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java63
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java55
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.form77
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.java59
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchiveDialog.java40
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form79
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.java65
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java153
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.form102
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java96
29 files changed, 2487 insertions, 0 deletions
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java
new file mode 100644
index 000000000000..1eb8690aad22
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java
@@ -0,0 +1,80 @@
+package org.jetbrains.plugins.coursecreator;
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.event.EditorFactoryEvent;
+import com.intellij.openapi.editor.event.EditorFactoryListener;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.format.*;
+
+public class CCEditorFactoryListener implements EditorFactoryListener {
+ @Override
+ public void editorCreated(@NotNull EditorFactoryEvent event) {
+ Editor editor = event.getEditor();
+ Project project = editor.getProject();
+ if (project == null) {
+ return;
+ }
+ VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(editor.getDocument());
+ if (virtualFile == null) {
+ return;
+ }
+ Course course = CCProjectService.getInstance(project).getCourse();
+ if (course == null) {
+ return;
+ }
+ final VirtualFile taskDir = virtualFile.getParent();
+ if (taskDir == null || !taskDir.getName().contains("task")) {
+ return;
+ }
+ final VirtualFile lessonDir = taskDir.getParent();
+ if (lessonDir == null) return;
+ final Lesson lesson = course.getLesson(lessonDir.getName());
+ final Task task = lesson.getTask(taskDir.getName());
+ final TaskFile taskFile = task.getTaskFile(virtualFile.getName());
+ TaskFileModificationListener listener = new TaskFileModificationListener(taskFile);
+ CCProjectService.addDocumentListener(editor.getDocument(), listener);
+ editor.getDocument().addDocumentListener(listener);
+ CCProjectService.drawTaskWindows(virtualFile, editor, course);
+ }
+
+ @Override
+ public void editorReleased(@NotNull EditorFactoryEvent event) {
+ Editor editor = event.getEditor();
+ Document document = editor.getDocument();
+ StudyDocumentListener listener = CCProjectService.getListener(document);
+ if (listener != null) {
+ document.removeDocumentListener(listener);
+ CCProjectService.removeListener(document);
+ }
+ editor.getMarkupModel().removeAllHighlighters();
+ editor.getSelectionModel().removeSelection();
+ }
+
+ private class TaskFileModificationListener extends StudyDocumentListener {
+
+ private final TaskFile myTaskFile;
+
+ public TaskFileModificationListener(TaskFile taskFile) {
+ super(taskFile);
+ myTaskFile = taskFile;
+ }
+
+ @Override
+ protected void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change) {
+ int newLength = taskWindow.getReplacementLength() + change;
+ taskWindow.setReplacementLength(newLength <= 0 ? 0 : newLength);
+ if (fragment.equals("\n")) {
+ taskWindow.setReplacementLength(taskWindow.getLength() + 1);
+ }
+ }
+
+ @Override
+ protected boolean needModify() {
+ return myTaskFile.isTrackChanges();
+ }
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java
new file mode 100644
index 000000000000..34de943d2ad0
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java
@@ -0,0 +1,58 @@
+package org.jetbrains.plugins.coursecreator;
+
+import com.intellij.openapi.components.ProjectComponent;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.event.EditorFactoryEvent;
+import com.intellij.openapi.editor.impl.EditorFactoryImpl;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.startup.StartupManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.format.Course;
+
+public class CCProjectComponent implements ProjectComponent {
+ private final Project myProject;
+
+ public CCProjectComponent(Project project) {
+ myProject = project;
+ }
+
+ public void initComponent() {
+ }
+
+ public void disposeComponent() {
+ }
+
+ @NotNull
+ public String getComponentName() {
+ return "CCProjectComponent";
+ }
+
+ public void projectOpened() {
+ StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() {
+ @Override
+ public void run() {
+ Course course = CCProjectService.getInstance(myProject).getCourse();
+ if (course != null) {
+ EditorFactory.getInstance().addEditorFactoryListener(new CCEditorFactoryListener(), myProject);
+ VirtualFile[] files = FileEditorManager.getInstance(myProject).getOpenFiles();
+ for (VirtualFile file : files) {
+ FileEditor fileEditor = FileEditorManager.getInstance(myProject).getSelectedEditor(file);
+ if (fileEditor instanceof PsiAwareTextEditorImpl) {
+ Editor editor = ((PsiAwareTextEditorImpl)fileEditor).getEditor();
+ new CCEditorFactoryListener().editorCreated(new EditorFactoryEvent(new EditorFactoryImpl(ProjectManager.getInstance()), editor ));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public void projectClosed() {
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectGenerator.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectGenerator.java
new file mode 100644
index 000000000000..dbaa7265f5c1
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectGenerator.java
@@ -0,0 +1,101 @@
+package org.jetbrains.plugins.coursecreator;
+
+import com.intellij.facet.ui.FacetEditorValidator;
+import com.intellij.facet.ui.FacetValidatorsManager;
+import com.intellij.facet.ui.ValidationResult;
+import com.intellij.ide.fileTemplates.FileTemplate;
+import com.intellij.ide.fileTemplates.FileTemplateManager;
+import com.intellij.ide.fileTemplates.FileTemplateUtil;
+import com.intellij.ide.util.DirectoryUtil;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.platform.DirectoryProjectGenerator;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.python.newProject.PythonProjectGenerator;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.ui.CCNewProjectPanel;
+
+import javax.swing.*;
+
+
+public class CCProjectGenerator extends PythonProjectGenerator implements DirectoryProjectGenerator {
+ private CCNewProjectPanel mySettingsPanel;
+
+ @Nls
+ @NotNull
+ @Override
+ public String getName() {
+ return "Course creation";
+ }
+
+ @Nullable
+ @Override
+ public Object showGenerationSettings(VirtualFile baseDir) throws ProcessCanceledException {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Icon getLogo() {
+ return null;
+ }
+
+
+ @Override
+ public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir,
+ @Nullable Object settings, @NotNull Module module) {
+
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = new Course(mySettingsPanel.getName(), mySettingsPanel.getAuthor(), mySettingsPanel.getDescription());
+ service.setCourse(course);
+
+ final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir);
+ if (projectDir == null) return;
+ new WriteCommandAction.Simple(project) {
+ @Override
+ protected void run() throws Throwable {
+ final FileTemplate template = FileTemplateManager.getInstance().getInternalTemplate("test_helper");
+ try {
+ FileTemplateUtil.createFromTemplate(template, "test_helper.py", null, projectDir);
+ }
+ catch (Exception ignored) {
+ }
+ DirectoryUtil.createSubdirectories("hints", projectDir, "\\/");
+ }
+ }.execute();
+
+ }
+
+ @NotNull
+ @Override
+ public ValidationResult validate(@NotNull String s) {
+ String message = "";
+ message = mySettingsPanel.getDescription().equals("") ? "Enter description" : message;
+ message = mySettingsPanel.getAuthor().equals("") ? "Enter author name" : message;
+ message = mySettingsPanel.getName().equals("") ? "Enter course name" : message;
+ return message.equals("")? ValidationResult.OK : new ValidationResult(message) ;
+ }
+
+ @Nullable
+ @Override
+ public JPanel extendBasePanel() throws ProcessCanceledException {
+ mySettingsPanel = new CCNewProjectPanel();
+ mySettingsPanel.registerValidators(new FacetValidatorsManager() {
+ public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void validate() {
+ fireStateChanged();
+ }
+ });
+ return mySettingsPanel.getMainPanel();
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java
new file mode 100644
index 000000000000..1e38bab865cb
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2000-2013 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator;
+
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.util.xmlb.XmlSerializer;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.format.*;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@State(name = "CCProjectService",
+ storages = {
+ @Storage(file = "$PROJECT_CONFIG_DIR$/course_service.xml")
+ }
+)
+public class CCProjectService implements PersistentStateComponent<Element> {
+
+ private static final Logger LOG = Logger.getInstance(CCProjectService.class.getName());
+ public Course myCourse;
+ public static final String COURSE_ELEMENT = "course";
+ private static final Map<Document, StudyDocumentListener> myDocumentListeners = new HashMap<Document, StudyDocumentListener>();
+
+ public void setCourse(@NotNull final Course course) {
+ myCourse = course;
+ }
+
+ public Course getCourse() {
+ return myCourse;
+ }
+
+ @Override
+ public Element getState() {
+ final Element el = new Element("CCProjectService");
+ if (myCourse != null) {
+ Element courseElement = new Element(COURSE_ELEMENT);
+ XmlSerializer.serializeInto(myCourse, courseElement);
+ el.addContent(courseElement);
+ }
+ return el;
+ }
+
+ @Override
+ public void loadState(Element el) {
+ myCourse = XmlSerializer.deserialize(el.getChild(COURSE_ELEMENT), Course.class);
+ }
+
+ public static CCProjectService getInstance(@NotNull Project project) {
+ return ServiceManager.getService(project, CCProjectService.class);
+ }
+
+ public static void deleteProjectFile(File file, @NotNull final Project project) {
+ if (!file.delete()) {
+ LOG.info("Failed to delete file " + file.getPath());
+ }
+ VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+ ProjectView.getInstance(project).refresh();
+ }
+
+ public static void drawTaskWindows(@NotNull final VirtualFile virtualFile, @NotNull final Editor editor, @NotNull final Course course) {
+ VirtualFile taskDir = virtualFile.getParent();
+ if (taskDir == null) {
+ return;
+ }
+ String taskDirName = taskDir.getName();
+ if (!taskDirName.contains("task")) {
+ return;
+ }
+ VirtualFile lessonDir = taskDir.getParent();
+ if (lessonDir == null) {
+ return;
+ }
+ String lessonDirName = lessonDir.getName();
+ if (!lessonDirName.contains("lesson")) {
+ return;
+ }
+ Lesson lesson = course.getLessonsMap().get(lessonDirName);
+ if (lesson == null) {
+ return;
+ }
+ Task task = lesson.getTask(taskDirName);
+ if (task == null) {
+ return;
+ }
+ TaskFile taskFile = task.getTaskFile(virtualFile.getName());
+ if (taskFile == null) {
+ return;
+ }
+ List<TaskWindow> taskWindows = taskFile.getTaskWindows();
+ for (TaskWindow taskWindow : taskWindows) {
+ taskWindow.drawHighlighter(editor);
+ }
+ }
+
+ public static void addDocumentListener(Document document, StudyDocumentListener listener) {
+ myDocumentListeners.put(document, listener);
+ }
+
+ public static StudyDocumentListener getListener(Document document) {
+ return myDocumentListeners.get(document);
+ }
+
+ public static void removeListener(Document document) {
+ myDocumentListeners.remove(document);
+ }
+
+ public static boolean indexIsValid(int index, List<TaskWindow> collection) {
+ int size = collection.size();
+ return index >= 0 && index < size;
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java
new file mode 100644
index 000000000000..d803e0e8fd97
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java
@@ -0,0 +1,71 @@
+package org.jetbrains.plugins.coursecreator;
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.editor.event.DocumentAdapter;
+import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
+import org.jetbrains.plugins.coursecreator.format.TaskFile;
+import org.jetbrains.plugins.coursecreator.format.TaskWindow;
+
+/**
+ * author: liana
+ * data: 7/16/14.
+ * Listens changes in study files and updates
+ * coordinates of all the windows in current task file
+ */
+public abstract class StudyDocumentListener extends DocumentAdapter {
+ private final TaskFile myTaskFile;
+ private int oldLine;
+ private int oldLineStartOffset;
+ private TaskWindow myTaskWindow;
+
+ public StudyDocumentListener(TaskFile taskFile) {
+ myTaskFile = taskFile;
+ }
+
+
+ //remembering old end before document change because of problems
+ // with fragments containing "\n"
+ @Override
+ public void beforeDocumentChange(DocumentEvent e) {
+ int offset = e.getOffset();
+ int oldEnd = offset + e.getOldLength();
+ Document document = e.getDocument();
+ oldLine = document.getLineNumber(oldEnd);
+ oldLineStartOffset = document.getLineStartOffset(oldLine);
+ int line = document.getLineNumber(offset);
+ int offsetInLine = offset - document.getLineStartOffset(line);
+ LogicalPosition pos = new LogicalPosition(line, offsetInLine);
+ myTaskWindow = myTaskFile.getTaskWindow(document, pos);
+
+ }
+
+ @Override
+ public void documentChanged(DocumentEvent e) {
+ if (e instanceof DocumentEventImpl) {
+ if (!needModify()) {
+ return;
+ }
+ DocumentEventImpl event = (DocumentEventImpl)e;
+ Document document = e.getDocument();
+ int offset = e.getOffset();
+ int change = event.getNewLength() - event.getOldLength();
+ if (myTaskWindow != null) {
+ updateTaskWindowLength(e.getNewFragment(), myTaskWindow, change);
+ }
+ int newEnd = offset + event.getNewLength();
+ int newLine = document.getLineNumber(newEnd);
+ int lineChange = newLine - oldLine;
+ myTaskFile.incrementLines(oldLine + 1, lineChange);
+ int newEndOffsetInLine = offset + e.getNewLength() - document.getLineStartOffset(newLine);
+ int oldEndOffsetInLine = offset + e.getOldLength() - oldLineStartOffset;
+ myTaskFile.updateLine(lineChange, oldLine, newEndOffsetInLine, oldEndOffsetInLine);
+ }
+ }
+
+ protected abstract void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change);
+
+ protected abstract boolean needModify();
+}
+
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java
new file mode 100644
index 000000000000..ff88cea5fd42
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java
@@ -0,0 +1,105 @@
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.SelectionModel;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.*;
+import org.jetbrains.plugins.coursecreator.ui.CreateTaskWindowDialog;
+
+public class AddTaskWindow extends DumbAwareAction {
+ public AddTaskWindow() {
+ super("Add task window","Add task window", null);
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final Project project = e.getData(CommonDataKeys.PROJECT);
+ if (project == null) {
+ return;
+ }
+ final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext());
+ if (file == null) return;
+ final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
+ if (editor == null) return;
+
+ final SelectionModel model = editor.getSelectionModel();
+ final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
+ if (document == null) return;
+ final int start = model.getSelectionStart();
+ final int end = model.getSelectionEnd();
+ final int lineNumber = document.getLineNumber(start);
+ final int length = end - start;
+ int realStart = start - document.getLineStartOffset(lineNumber);
+
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final PsiDirectory taskDir = file.getContainingDirectory();
+ final PsiDirectory lessonDir = taskDir.getParent();
+ if (lessonDir == null) return;
+
+ final Lesson lesson = course.getLesson(lessonDir.getName());
+ final Task task = lesson.getTask(taskDir.getName());
+ final TaskFile taskFile = task.getTaskFile(file.getName());
+ final TaskWindow taskWindow = new TaskWindow(lineNumber, realStart, length, model.getSelectedText());
+ CreateTaskWindowDialog dlg = new CreateTaskWindowDialog(project, taskWindow, lesson.getIndex(), task.getIndex(), file.getVirtualFile().getNameWithoutExtension(), taskFile.getTaskWindows().size() + 1);
+ dlg.show();
+ if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+ return;
+ }
+ int index = taskFile.getTaskWindows().size() + 1;
+ taskFile.addTaskWindow(taskWindow, index);
+ taskWindow.drawHighlighter(editor);
+ DaemonCodeAnalyzerImpl.getInstance(project).restart(file);
+ }
+
+ @Override
+ public void update(AnActionEvent event) {
+ final Presentation presentation = event.getPresentation();
+ final Project project = event.getData(CommonDataKeys.PROJECT);
+ if (project == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ final Editor editor = CommonDataKeys.EDITOR.getData(event.getDataContext());
+ final PsiFile file = CommonDataKeys.PSI_FILE.getData(event.getDataContext());
+ if (editor == null || file == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ if (!editor.getSelectionModel().hasSelection()) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final PsiDirectory taskDir = file.getContainingDirectory();
+ final PsiDirectory lessonDir = taskDir.getParent();
+ if (lessonDir == null) return;
+
+ final Lesson lesson = course.getLesson(lessonDir.getName());
+ final Task task = lesson.getTask(taskDir.getName());
+ if (task == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ presentation.setVisible(true);
+ presentation.setEnabled(true);
+
+ }
+} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java
new file mode 100644
index 000000000000..05428f4e82d1
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java
@@ -0,0 +1,198 @@
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.util.io.ZipUtil;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.StudyDocumentListener;
+import org.jetbrains.plugins.coursecreator.format.*;
+import org.jetbrains.plugins.coursecreator.ui.CreateCourseArchiveDialog;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.ZipOutputStream;
+
+public class CreateCourseArchive extends DumbAwareAction {
+ private static final Logger LOG = Logger.getInstance(CreateCourseArchive.class.getName());
+ String myZipName;
+ String myLocationDir;
+
+ public void setZipName(String zipName) {
+ myZipName = zipName;
+ }
+
+ public void setLocationDir(String locationDir) {
+ myLocationDir = locationDir;
+ }
+
+ public CreateCourseArchive() {
+ super("Generate course archive", "Generate course archive", AllIcons.FileTypes.Archive);
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final Project project = e.getData(CommonDataKeys.PROJECT);
+ if (project == null) {
+ return;
+ }
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ if (course == null) return;
+ CreateCourseArchiveDialog dlg = new CreateCourseArchiveDialog(project, this);
+ dlg.show();
+ if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+ return;
+ }
+ final VirtualFile baseDir = project.getBaseDir();
+ final Map<String, Lesson> lessons = course.getLessonsMap();
+ //List<FileEditor> editorList = new ArrayList<FileEditor>();
+ Map<VirtualFile, TaskFile> taskFiles = new HashMap<VirtualFile, TaskFile>();
+ for (Map.Entry<String, Lesson> lesson : lessons.entrySet()) {
+ final VirtualFile lessonDir = baseDir.findChild(lesson.getKey());
+ if (lessonDir == null) continue;
+ for (Map.Entry<String, Task> task : lesson.getValue().myTasksMap.entrySet()) {
+ final VirtualFile taskDir = lessonDir.findChild(task.getKey());
+ if (taskDir == null) continue;
+ for (Map.Entry<String, TaskFile> entry : task.getValue().task_files.entrySet()) {
+ final VirtualFile file = taskDir.findChild(entry.getKey());
+ if (file == null) continue;
+ final Document document = FileDocumentManager.getInstance().getDocument(file);
+ if (document == null) continue;
+ final TaskFile taskFile = entry.getValue();
+ document.addDocumentListener(new InsertionListener(taskFile));
+ taskFiles.put(file, taskFile);
+ taskFile.setTrackChanges(false);
+ Collections.sort(taskFile.getTaskWindows());
+ for (int i = taskFile.getTaskWindows().size() - 1; i >=0 ; i--) {
+ final TaskWindow taskWindow = taskFile.getTaskWindows().get(i);
+ final String taskText = taskWindow.getTaskText();
+ final int lineStartOffset = document.getLineStartOffset(taskWindow.line);
+ final int offset = lineStartOffset + taskWindow.start;
+ CommandProcessor.getInstance().executeCommand(project, new Runnable() {
+ @Override
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ document.replaceString(offset, offset + taskWindow.getReplacementLength(), taskText);
+ FileDocumentManager.getInstance().saveDocument(document);
+ }
+ });
+ }
+ }, "x", "qwe");
+ }
+ }
+ }
+ }
+ generateJson(project);
+ try {
+ File zipFile = new File(myLocationDir, myZipName + ".zip");
+ ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
+
+ for (Map.Entry<String, Lesson> entry : lessons.entrySet()) {
+ final VirtualFile lessonDir = baseDir.findChild(entry.getKey());
+ if (lessonDir == null) continue;
+
+ ZipUtil.addFileOrDirRecursively(zos, null, new File(lessonDir.getPath()), lessonDir.getName(), null, null);
+ }
+ ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "hints"), "hints", null, null);
+ ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "course.json"), "course.json", null, null);
+ ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "test_helper.py"), "test_helper.py", null, null);
+ zos.close();
+ Messages.showInfoMessage("Course archive was saved to " + zipFile.getPath(), "Course Archive Was Created Successfully");
+ }
+ catch (IOException e1) {
+ LOG.error(e1);
+ }
+
+ for (Map.Entry<VirtualFile, TaskFile> entry: taskFiles.entrySet()) {
+ TaskFile value = entry.getValue();
+ final Document document = FileDocumentManager.getInstance().getDocument(entry.getKey());
+ if (document == null) {
+ continue;
+ }
+ for (final TaskWindow taskWindow : value.getTaskWindows()){
+ final int lineStartOffset = document.getLineStartOffset(taskWindow.line);
+ final int offset = lineStartOffset + taskWindow.start;
+ CommandProcessor.getInstance().executeCommand(project, new Runnable() {
+ @Override
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ document.replaceString(offset, offset + taskWindow.length, taskWindow.getPossibleAnswer());
+ FileDocumentManager.getInstance().saveDocument(document);
+ }
+ });
+ }
+ }, "x", "qwe");
+ }
+ value.setTrackChanges(true);
+ }
+ VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+ ProjectView.getInstance(project).refresh();
+ }
+
+ private void generateJson(Project project) {
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
+ final String json = gson.toJson(course);
+ final File courseJson = new File(project.getBasePath(), "course.json");
+ FileWriter writer = null;
+ try {
+ writer = new FileWriter(courseJson);
+ writer.write(json);
+ }
+ catch (IOException e) {
+ Messages.showErrorDialog(e.getMessage(), "Failed to Generate Json");
+ LOG.info(e);
+ }
+ catch (Exception e) {
+ Messages.showErrorDialog(e.getMessage(), "Failed to Generate Json");
+ LOG.info(e);
+ }
+ finally {
+ try {
+ if (writer != null) {
+ writer.close();
+ }
+ }
+ catch (IOException e1) {
+ //close silently
+ }
+ }
+ }
+
+ private class InsertionListener extends StudyDocumentListener {
+
+ public InsertionListener(TaskFile taskFile) {
+ super(taskFile);
+ }
+
+ @Override
+ protected void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change) {
+ //we don't need to update task window length
+ }
+
+ @Override
+ protected boolean needModify() {
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateLesson.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateLesson.java
new file mode 100644
index 000000000000..15d9f8367db9
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateLesson.java
@@ -0,0 +1,89 @@
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.ide.util.DirectoryUtil;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.format.Lesson;
+
+public class CreateLesson extends DumbAwareAction {
+ public CreateLesson() {
+ super("Lesson", "Create new Lesson", PlatformIcons.DIRECTORY_CLOSED_ICON);
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
+ final Project project = e.getData(CommonDataKeys.PROJECT);
+
+ if (view == null || project == null) {
+ return;
+ }
+ final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
+ if (directory == null) return;
+
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final int size = course.getLessons().size();
+ final String lessonName = Messages.showInputDialog("Name:", "Lesson Name", null, "lesson" + (size+1), null);
+ if (lessonName == null) return;
+
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ final PsiDirectory lessonDirectory = DirectoryUtil.createSubdirectories("lesson" + (size+1), directory, "\\/");
+ if (lessonDirectory != null) {
+ view.selectElement(lessonDirectory);
+ final Lesson lesson = new Lesson(lessonName);
+ lesson.setIndex(size + 1);
+ course.addLesson(lesson, lessonDirectory);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void update(AnActionEvent event) {
+ final Presentation presentation = event.getPresentation();
+ final Project project = event.getData(CommonDataKeys.PROJECT);
+ if (project == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
+ if (view == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final PsiDirectory[] directories = view.getDirectories();
+ if (directories.length == 0) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
+ if (directory != null && !project.getBaseDir().equals(directory.getVirtualFile())) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ presentation.setVisible(true);
+ presentation.setEnabled(true);
+
+ }
+} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java
new file mode 100644
index 000000000000..0940135b97be
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java
@@ -0,0 +1,122 @@
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.fileTemplates.FileTemplate;
+import com.intellij.ide.fileTemplates.FileTemplateManager;
+import com.intellij.ide.fileTemplates.FileTemplateUtil;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.ide.util.DirectoryUtil;
+import com.intellij.ide.util.EditorHelper;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.format.Lesson;
+import org.jetbrains.plugins.coursecreator.format.Task;
+
+public class CreateTask extends DumbAwareAction {
+ public CreateTask() {
+ super("Task", "Create new Task", PlatformIcons.DIRECTORY_CLOSED_ICON);
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
+ final Project project = e.getData(CommonDataKeys.PROJECT);
+
+ if (view == null || project == null) {
+ return;
+ }
+ final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
+
+ if (directory == null) return;
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final Lesson lesson = course.getLesson(directory.getName());
+ final int size = lesson.getTasklist().size();
+
+ final String taskName = Messages.showInputDialog("Name:", "Task Name", null, "task" + (size + 1), null);
+ if (taskName == null) return;
+
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ final PsiDirectory taskDirectory = DirectoryUtil.createSubdirectories("task" + (size + 1), directory, "\\/");
+ if (taskDirectory != null) {
+ final FileTemplate template = FileTemplateManager.getInstance().getInternalTemplate("task.html");
+ final FileTemplate testsTemplate = FileTemplateManager.getInstance().getInternalTemplate("tests");
+ final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.py");
+ try {
+ final PsiElement taskFile = FileTemplateUtil.createFromTemplate(template, "task.html", null, taskDirectory);
+ final PsiElement testsFile = FileTemplateUtil.createFromTemplate(testsTemplate, "tests.py", null, taskDirectory);
+ final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, "file1" + ".py", null, taskDirectory);
+
+ final Task task = new Task(taskName);
+ task.addTaskFile(taskPyFile.getContainingFile().getName(), size + 1);
+ task.setIndex(size + 1);
+ lesson.addTask(task, taskDirectory);
+
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ EditorHelper.openInEditor(testsFile, false);
+ EditorHelper.openInEditor(taskPyFile, false);
+ view.selectElement(taskFile);
+ }
+ });
+ }
+ catch (Exception ignored) {
+ }
+
+
+ }
+ }
+ });
+ }
+
+ @Override
+ public void update(AnActionEvent event) {
+ final Presentation presentation = event.getPresentation();
+ final Project project = event.getData(CommonDataKeys.PROJECT);
+ if (project == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
+ if (view == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final PsiDirectory[] directories = view.getDirectories();
+ if (directories.length == 0) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ if (course != null && directory != null && course.getLesson(directory.getName()) == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ presentation.setVisible(true);
+ presentation.setEnabled(true);
+
+ }
+} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java
new file mode 100644
index 000000000000..5aafcebefd6e
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java
@@ -0,0 +1,110 @@
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.fileTemplates.FileTemplate;
+import com.intellij.ide.fileTemplates.FileTemplateManager;
+import com.intellij.ide.fileTemplates.FileTemplateUtil;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.ide.util.EditorHelper;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import icons.PythonPsiApiIcons;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.format.Lesson;
+import org.jetbrains.plugins.coursecreator.format.Task;
+
+public class CreateTaskFile extends DumbAwareAction {
+
+ public CreateTaskFile() {
+ super("Task File", "Create new Task File", PythonPsiApiIcons.PythonFile);
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
+ final Project project = e.getData(CommonDataKeys.PROJECT);
+
+ if (view == null || project == null) {
+ return;
+ }
+ final PsiDirectory taskDir = DirectoryChooserUtil.getOrChooseDirectory(view);
+ if (taskDir == null) return;
+ PsiDirectory lessonDir = taskDir.getParent();
+ if (lessonDir == null) {
+ return;
+ }
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final Lesson lesson = course.getLesson(lessonDir.getName());
+ final Task task = lesson.getTask(taskDir.getName());
+
+ final int index = task.getTaskFiles().size() + 1;
+ String generatedName = "file" + index;
+ final String taskFileName = Messages.showInputDialog("Name:", "Task File Name", null, generatedName, null);
+ if (taskFileName == null) return;
+
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.py");
+ try {
+ final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, taskFileName + ".py", null, taskDir);
+ task.addTaskFile(taskPyFile.getContainingFile().getName(), index);
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ EditorHelper.openInEditor(taskPyFile, false);
+ view.selectElement(taskPyFile);
+ }
+ });
+ }
+ catch (Exception ignored) {
+ }
+ }
+ });
+ }
+
+ @Override
+ public void update(AnActionEvent event) {
+ final Presentation presentation = event.getPresentation();
+ final Project project = event.getData(CommonDataKeys.PROJECT);
+ if (project == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
+ if (view == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final PsiDirectory[] directories = view.getDirectories();
+ if (directories.length == 0) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ if (course != null && directory != null && !directory.getName().contains("task")) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ presentation.setVisible(true);
+ presentation.setEnabled(true);
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/DeleteTaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/DeleteTaskWindow.java
new file mode 100644
index 000000000000..2724759a8c4f
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/DeleteTaskWindow.java
@@ -0,0 +1,62 @@
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.*;
+
+import java.util.List;
+
+@SuppressWarnings("ComponentNotRegistered")
+public class DeleteTaskWindow extends DumbAwareAction {
+ @NotNull
+ private final TaskWindow myTaskWindow;
+
+ public DeleteTaskWindow(@NotNull final TaskWindow taskWindow) {
+ super("Delete task window","Delete task window", null);
+ myTaskWindow = taskWindow;
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final Project project = e.getData(PlatformDataKeys.PROJECT);
+ if (project == null) return;
+ final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext());
+ if (file == null) return;
+ final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
+ if (editor == null) {
+ return;
+ }
+ final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
+ if (document == null) return;
+
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final PsiDirectory taskDir = file.getContainingDirectory();
+ final PsiDirectory lessonDir = taskDir.getParent();
+ if (lessonDir == null) return;
+
+ final Lesson lesson = course.getLesson(lessonDir.getName());
+ final Task task = lesson.getTask(taskDir.getName());
+ final TaskFile taskFile = task.getTaskFile(file.getName());
+ final List<TaskWindow> taskWindows = taskFile.getTaskWindows();
+ if (taskWindows.contains(myTaskWindow)) {
+ myTaskWindow.removeResources(project);
+ taskWindows.remove(myTaskWindow);
+ editor.getMarkupModel().removeAllHighlighters();
+ CCProjectService.drawTaskWindows(file.getVirtualFile(), editor, course);
+ DaemonCodeAnalyzerImpl.getInstance(project).restart(file);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/ShowTaskWindowText.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/ShowTaskWindowText.java
new file mode 100644
index 000000000000..7c7e7fa727b8
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/ShowTaskWindowText.java
@@ -0,0 +1,44 @@
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.*;
+import org.jetbrains.plugins.coursecreator.ui.CreateTaskWindowDialog;
+
+@SuppressWarnings("ComponentNotRegistered")
+public class ShowTaskWindowText extends DumbAwareAction {
+ @NotNull
+ private final TaskWindow myTaskWindow;
+
+ public ShowTaskWindowText(@NotNull final TaskWindow taskWindow) {
+ super("Add task window","Add task window", null);
+ myTaskWindow = taskWindow;
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final Project project = e.getData(PlatformDataKeys.PROJECT);
+ if (project == null) return;
+ final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext());
+ if (file == null) return;
+ final CCProjectService service = CCProjectService.getInstance(project);
+ final Course course = service.getCourse();
+ final PsiDirectory taskDir = file.getContainingDirectory();
+ final PsiDirectory lessonDir = taskDir.getParent();
+ if (lessonDir == null) return;
+
+ final Lesson lesson = course.getLesson(lessonDir.getName());
+ final Task task = lesson.getTask(taskDir.getName());
+ final TaskFile taskFile = task.getTaskFile(file.getName());
+ //TODO: copy task window and return if modification canceled
+ CreateTaskWindowDialog dlg = new CreateTaskWindowDialog(project, myTaskWindow, lesson.getIndex(), task.getIndex(), file.getVirtualFile().getNameWithoutExtension(), taskFile.getTaskWindows().size() + 1);
+ dlg.show();
+ }
+} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java
new file mode 100644
index 000000000000..eb62d59cd9b1
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java
@@ -0,0 +1,55 @@
+package org.jetbrains.plugins.coursecreator.format;
+
+import com.google.gson.annotations.Expose;
+import com.intellij.psi.PsiDirectory;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Course {
+ @Expose public List<Lesson> lessons = new ArrayList<Lesson>();
+ @Expose public String description;
+
+ @Expose public String name;
+ @Expose public String author;
+
+ public Map<String, Lesson> myLessonsMap = new HashMap<String, Lesson>();
+
+ public Map<String, Lesson> getLessonsMap() {
+ return myLessonsMap;
+ }
+
+ public Lesson getLesson(@NotNull final String name) {
+ return myLessonsMap.get(name);
+ }
+
+
+ public Course() {
+ }
+
+ public Course(@NotNull final String name, @NotNull final String author, @NotNull final String description) {
+ this.description = description;
+ this.name = name;
+ this.author = author;
+ }
+
+ public List<Lesson> getLessons() {
+ return lessons;
+ }
+
+ public void addLesson(@NotNull final Lesson lesson, @NotNull final PsiDirectory directory) {
+ lessons.add(lesson);
+ myLessonsMap.put(directory.getName(), lesson);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java
new file mode 100644
index 000000000000..38720140caf1
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java
@@ -0,0 +1,45 @@
+package org.jetbrains.plugins.coursecreator.format;
+
+import com.google.gson.annotations.Expose;
+import com.intellij.psi.PsiDirectory;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Lesson {
+ @Expose public String name;
+ @Expose public List<Task> task_list = new ArrayList<Task>();
+
+ public int myIndex;
+ public Map<String, Task> myTasksMap = new HashMap<String, Task>();
+
+ public Lesson() {}
+
+ public Lesson(@NotNull final String name) {
+ this.name = name;
+ }
+
+ public void addTask(@NotNull final Task task, PsiDirectory taskDirectory) {
+ myTasksMap.put(taskDirectory.getName(), task);
+ task_list.add(task);
+ }
+
+ public Task getTask(@NotNull final String name) {
+ return myTasksMap.get(name);
+ }
+
+ public List<Task> getTasklist() {
+ return task_list;
+ }
+
+ public void setIndex(int index) {
+ myIndex = index;
+ }
+
+ public int getIndex() {
+ return myIndex;
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java
new file mode 100644
index 000000000000..e6c085b5d6a1
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java
@@ -0,0 +1,41 @@
+package org.jetbrains.plugins.coursecreator.format;
+
+import com.google.gson.annotations.Expose;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Task {
+ @Expose public String name;
+ @Expose public Map<String, TaskFile> task_files = new HashMap<String, TaskFile>();
+ public int myIndex;
+
+ public Task() {}
+
+ public Task(@NotNull final String name) {
+ this.name = name;
+ }
+
+ public int getIndex() {
+ return myIndex;
+ }
+
+ public void addTaskFile(@NotNull final String name, int index) {
+ TaskFile taskFile = new TaskFile();
+ taskFile.setIndex(index);
+ task_files.put(name, taskFile);
+ }
+
+ public TaskFile getTaskFile(@NotNull final String name) {
+ return task_files.get(name);
+ }
+
+ public void setIndex(int index) {
+ myIndex = index;
+ }
+
+ public Map<String, TaskFile> getTaskFiles() {
+ return task_files;
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java
new file mode 100644
index 000000000000..85f0d91983f2
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java
@@ -0,0 +1,111 @@
+package org.jetbrains.plugins.coursecreator.format;
+
+import com.google.gson.annotations.Expose;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.LogicalPosition;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TaskFile {
+ @Expose public List<TaskWindow> task_windows = new ArrayList<TaskWindow>();
+ public int myIndex;
+ public boolean myTrackChanges = true;
+
+ public boolean isTrackChanges() {
+ return myTrackChanges;
+ }
+
+ public void setTrackChanges(boolean trackChanges) {
+ myTrackChanges = trackChanges;
+ }
+
+ public TaskFile() {}
+
+ public void addTaskWindow(@NotNull final TaskWindow taskWindow, int index) {
+ taskWindow.setIndex(index);
+ task_windows.add(taskWindow);
+ }
+
+ public List<TaskWindow> getTaskWindows() {
+ return task_windows;
+ }
+
+ public void setIndex(int index) {
+ myIndex = index;
+ }
+
+
+ /**
+ * @param pos position in editor
+ * @return task window located in specified position or null if there is no task window in this position
+ */
+ @Nullable
+ public TaskWindow getTaskWindow(@NotNull final Document document, @NotNull final LogicalPosition pos) {
+ int line = pos.line;
+ if (line >= document.getLineCount()) {
+ return null;
+ }
+ int column = pos.column;
+ int offset = document.getLineStartOffset(line) + column;
+ for (TaskWindow tw : task_windows) {
+ if (tw.getLine() <= line) {
+ int twStartOffset = tw.getRealStartOffset(document);
+ final int length = tw.getReplacementLength() > 0 ? tw.getReplacementLength() : 0;
+ int twEndOffset = twStartOffset + length;
+ if (twStartOffset <= offset && offset <= twEndOffset) {
+ return tw;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Updates task window lines
+ *
+ * @param startLine lines greater than this line and including this line will be updated
+ * @param change change to be added to line numbers
+ */
+ public void incrementLines(int startLine, int change) {
+ for (TaskWindow taskTaskWindow : task_windows) {
+ if (taskTaskWindow.getLine() >= startLine) {
+ taskTaskWindow.setLine(taskTaskWindow.getLine() + change);
+ }
+ }
+ }
+
+ /**
+ * Updates windows in specific line
+ *
+ * @param lineChange change in line number
+ * @param line line to be updated
+ * @param newEndOffsetInLine distance from line start to end of inserted fragment
+ * @param oldEndOffsetInLine distance from line start to end of changed fragment
+ */
+ public void updateLine(int lineChange, int line, int newEndOffsetInLine, int oldEndOffsetInLine) {
+ for (TaskWindow w : task_windows) {
+ if ((w.getLine() == line) && (w.getStart() >= oldEndOffsetInLine)) {
+ int distance = w.getStart() - oldEndOffsetInLine;
+ boolean coveredByPrevTW = false;
+ int prevIndex = w.getIndex() - 1;
+ if (CCProjectService.indexIsValid(prevIndex, task_windows)) {
+ TaskWindow prevTW = task_windows.get(prevIndex);
+ if (prevTW.getLine() == line) {
+ int endOffset = prevTW.getStart() + prevTW.getLength();
+ if (endOffset >= newEndOffsetInLine) {
+ coveredByPrevTW = true;
+ }
+ }
+ }
+ if (lineChange != 0 || newEndOffsetInLine <= w.getStart() || coveredByPrevTW) {
+ w.setStart(distance + newEndOffsetInLine);
+ w.setLine(line + lineChange);
+ }
+ }
+ }
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java
new file mode 100644
index 000000000000..cb6418ec75d7
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java
@@ -0,0 +1,133 @@
+package org.jetbrains.plugins.coursecreator.format;
+
+import com.google.gson.annotations.Expose;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.markup.HighlighterLayer;
+import com.intellij.openapi.editor.markup.HighlighterTargetArea;
+import com.intellij.openapi.editor.markup.RangeHighlighter;
+import com.intellij.openapi.editor.markup.TextAttributes;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+
+import java.io.File;
+
+public class TaskWindow implements Comparable{
+
+ @Expose public int line;
+ @Expose public int start;
+ @Expose public String hint;
+ @Expose public String possible_answer;
+ @Expose public int length;
+ public String myTaskText;
+ public int myReplacementLength;
+ public int myIndex;
+
+ public TaskWindow() {}
+
+ public TaskWindow(int line, int start, int length, String selectedText) {
+ this.line = line;
+ this.start = start;
+ myReplacementLength = length;
+ this.possible_answer = selectedText;
+ }
+
+ public void setTaskText(@NotNull final String taskText) {
+ myTaskText = taskText;
+ length = myTaskText.length();
+ }
+
+ public String getTaskText() {
+ return myTaskText;
+ }
+
+ public int getReplacementLength() {
+ return myReplacementLength;
+ }
+
+ public void setHint(String hint) {
+ this.hint = hint;
+ }
+
+ public String getHintName() {
+ return hint;
+ }
+
+ public void removeResources(@NotNull final Project project) {
+ if (hint != null) {
+ VirtualFile hints = project.getBaseDir().findChild("hints");
+ if (hints == null) {
+ return;
+ }
+ File hintFile = new File(hints.getPath(), hint);
+ CCProjectService.deleteProjectFile(hintFile, project);
+ }
+ }
+
+ public void drawHighlighter(@NotNull final Editor editor) {
+ int startOffset = editor.getDocument().getLineStartOffset(line) + start;
+ int endOffset = startOffset + myReplacementLength;
+ TextAttributes defaultTestAttributes =
+ EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES);
+ RangeHighlighter highlighter =
+ editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, HighlighterLayer.LAST + 1, defaultTestAttributes,
+ HighlighterTargetArea.EXACT_RANGE);
+ highlighter.setGreedyToLeft(true);
+ highlighter.setGreedyToRight(true);
+ }
+
+ public int getIndex() {
+ return myIndex;
+ }
+
+ public void setIndex(int index) {
+
+ myIndex = index;
+ }
+
+ public void setReplacementLength(int replacementLength) {
+ myReplacementLength = replacementLength;
+ }
+
+ public int getLine() {
+ return line;
+ }
+
+ public int getRealStartOffset(Document document) {
+ return document.getLineStartOffset(line) + start;
+ }
+
+ public void setLine(int line) {
+ this.line = line;
+ }
+
+ public int getStart() {
+ return start;
+ }
+
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ @Override
+ public int compareTo(Object o) {
+ TaskWindow taskWindow = (TaskWindow)o;
+ int lineDiff = line - taskWindow.line;
+ if (lineDiff == 0) {
+ return start - taskWindow.start;
+ }
+ return lineDiff;
+ }
+
+ public String getPossibleAnswer() {
+ return possible_answer;
+ }
+
+ public int getLength() {
+ return length;
+ }
+} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/CCTaskLineMarkerProvider.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/CCTaskLineMarkerProvider.java
new file mode 100644
index 000000000000..1818b658ddfc
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/CCTaskLineMarkerProvider.java
@@ -0,0 +1,75 @@
+package org.jetbrains.plugins.coursecreator.highlighting;
+
+import com.intellij.codeHighlighting.Pass;
+import com.intellij.codeInsight.daemon.LineMarkerInfo;
+import com.intellij.codeInsight.daemon.LineMarkerProvider;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.markup.GutterIconRenderer;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.IconLoader;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.*;
+
+import java.util.Collection;
+import java.util.List;
+
+public class CCTaskLineMarkerProvider implements LineMarkerProvider {
+ private static final Logger LOG = Logger.getInstance(CCTaskLineMarkerProvider.class.getName());
+
+ @Nullable
+ @Override
+ public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
+ return null;
+ }
+
+ @Override
+ public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull final Collection<LineMarkerInfo> result) {
+ for (PsiElement element : elements) {
+ if (element instanceof PsiFile) {
+ final Project project = element.getProject();
+ final Course course = CCProjectService.getInstance(project).getCourse();
+ if (course == null) return;
+ final String taskFileName = ((PsiFile) element).getName();
+ final PsiDirectory taskDir = ((PsiFile) element).getParent();
+ if (taskDir == null) continue;
+ final String taskDirName = taskDir.getName();
+ final PsiDirectory lessonDir = taskDir.getParentDirectory();
+ if (lessonDir == null) continue;
+ final String lessonDirName = lessonDir.getName();
+ final Lesson lesson = course.getLesson(lessonDirName);
+ if (lesson == null) continue;
+ final Task task = lesson.getTask(taskDirName);
+ final TaskFile taskFile = task.getTaskFile(taskFileName);
+ if (taskFile == null) continue;
+ final Document document = PsiDocumentManager.getInstance(project).getDocument((PsiFile) element);
+ if (document == null) continue;
+ for (final TaskWindow taskWindow : taskFile.getTaskWindows()) {
+ if (taskWindow.line > document.getLineCount()) continue;
+ final int lineStartOffset = document.getLineStartOffset(taskWindow.line);
+ final int offset = lineStartOffset + taskWindow.start;
+ if (offset > document.getTextLength()) continue;
+ final TextRange textRange = TextRange.create(offset, offset + taskWindow.getReplacementLength());
+ @SuppressWarnings("unchecked")
+ final LineMarkerInfo info = new LineMarkerInfo(element, textRange,
+ IconLoader.getIcon("/icons/gutter.png"), Pass.UPDATE_OVERRIDEN_MARKERS,
+ null, null, GutterIconRenderer.Alignment.CENTER) {
+ @Nullable
+ @Override
+ public GutterIconRenderer createGutterRenderer() {
+ return new TaskTextGutter(taskWindow, this);
+ }
+ };
+ result.add(info);
+ }
+ }
+ }
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/TaskTextGutter.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/TaskTextGutter.java
new file mode 100644
index 000000000000..80b267485192
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/TaskTextGutter.java
@@ -0,0 +1,60 @@
+package org.jetbrains.plugins.coursecreator.highlighting;
+
+import com.intellij.codeInsight.daemon.LineMarkerInfo;
+import com.intellij.openapi.actionSystem.ActionGroup;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.util.IconLoader;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.actions.DeleteTaskWindow;
+import org.jetbrains.plugins.coursecreator.actions.ShowTaskWindowText;
+import org.jetbrains.plugins.coursecreator.format.TaskWindow;
+
+import javax.swing.*;
+
+public class TaskTextGutter extends LineMarkerInfo.LineMarkerGutterIconRenderer {
+ @NotNull
+ private final TaskWindow myTaskWindow;
+
+ public TaskTextGutter(@NotNull final TaskWindow taskWindow, LineMarkerInfo lineMarkerInfo) {
+ super(lineMarkerInfo);
+ myTaskWindow = taskWindow;
+ }
+
+ @NotNull
+ @Override
+ public Icon getIcon() {
+ return IconLoader.getIcon("/icons/gutter.png");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || o instanceof TaskTextGutter
+ && myTaskWindow.getTaskText().equals(((TaskTextGutter) o).getTaskWindow().getTaskText());
+ }
+
+ @NotNull
+ public TaskWindow getTaskWindow() {
+ return myTaskWindow;
+ }
+
+ @Override
+ public int hashCode() {
+ return myTaskWindow.hashCode();
+ }
+
+ @Nullable
+ @Override
+ public AnAction getClickAction() {
+ return new ShowTaskWindowText(myTaskWindow);
+ }
+
+ @Nullable
+ @Override
+ public ActionGroup getPopupMenuActions() {
+ DefaultActionGroup group = new DefaultActionGroup();
+ group.add(new DeleteTaskWindow(myTaskWindow));
+ return group;
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java
new file mode 100644
index 000000000000..1a7304123f0a
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java
@@ -0,0 +1,63 @@
+package org.jetbrains.plugins.coursecreator.projectView;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.ui.SimpleTextAttributes;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.format.Lesson;
+import org.jetbrains.plugins.coursecreator.format.Task;
+
+public class CCDirectoryNode extends PsiDirectoryNode {
+ private final PsiDirectory myValue;
+ private final Project myProject;
+
+ public CCDirectoryNode(@NotNull final Project project,
+ PsiDirectory value,
+ ViewSettings viewSettings) {
+ super(project, value, viewSettings);
+ myValue = value;
+ myProject = project;
+ }
+
+ @Override
+ protected void updateImpl(PresentationData data) {
+ String valueName = myValue.getName();
+ final Course course = CCProjectService.getInstance(myProject).getCourse();
+ if (course == null) return;
+ if (myProject.getBaseDir().equals(myValue.getVirtualFile())) {
+ data.clearText();
+ data.addText(valueName, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ data.addText(" (" + course.getName() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES);
+ return;
+ }
+ final Lesson lesson = course.getLesson(valueName);
+ if (lesson != null) {
+ data.clearText();
+ data.addText(valueName, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ data.addText(" (" + lesson.name + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES);
+ return;
+ }
+ else {
+ final PsiDirectory parentDir = myValue.getParentDirectory();
+ if (parentDir != null) {
+ final Lesson parentLesson = course.getLesson(parentDir.getName());
+ if (parentLesson != null) {
+ final Task task = parentLesson.getTask(valueName);
+ if (task != null) {
+ data.clearText();
+ data.addText(valueName, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ data.addText(" (" + task.name + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES);
+ return;
+ }
+ }
+ }
+ }
+ data.setPresentableText(valueName);
+ }
+
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java
new file mode 100644
index 000000000000..69b78ec9a92b
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java
@@ -0,0 +1,55 @@
+package org.jetbrains.plugins.coursecreator.projectView;
+
+import com.intellij.ide.projectView.TreeStructureProvider;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class CCTreeStructureProvider implements TreeStructureProvider, DumbAware {
+ @NotNull
+ @Override
+ public Collection<AbstractTreeNode> modify(@NotNull AbstractTreeNode parent,
+ @NotNull Collection<AbstractTreeNode> children,
+ ViewSettings settings) {
+ if (!needModify(parent)) {
+ return children;
+ }
+ Collection<AbstractTreeNode> nodes = new ArrayList<AbstractTreeNode>();
+ for (AbstractTreeNode node : children) {
+ Project project = node.getProject();
+ if (project != null) {
+ if (node.getValue() instanceof PsiDirectory) {
+ PsiDirectory directory = (PsiDirectory) node.getValue();
+ nodes.add(new CCDirectoryNode(project, directory, settings));
+ } else {
+ nodes.add(node);
+ }
+ }
+ }
+ return nodes;
+ }
+
+ private static boolean needModify(@NotNull final AbstractTreeNode parent) {
+ Project project = parent.getProject();
+ if (project != null) {
+ if (CCProjectService.getInstance(project).getCourse() == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Object getData(Collection<AbstractTreeNode> selected, String dataName) {
+ return null;
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.form b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.form
new file mode 100644
index 000000000000..71e27857a2ee
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.form
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.coursecreator.ui.CCNewProjectPanel">
+ <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="500" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="fd520" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false">
+ <preferred-size width="95" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <text value="Name:"/>
+ </properties>
+ </component>
+ <component id="7e88" class="javax.swing.JTextField" binding="myName">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="ec56c" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Description"/>
+ </properties>
+ </component>
+ <component id="2e2d7" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false">
+ <preferred-size width="95" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <text value="Author:"/>
+ </properties>
+ </component>
+ <component id="41fe6" class="javax.swing.JTextField" binding="myAuthorField">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <grid id="12f06" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="2" column="1" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="line">
+ <color color="-6709600"/>
+ </border>
+ <children>
+ <component id="389a7" class="javax.swing.JTextArea" binding="myDescription">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="50"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ </children>
+ </grid>
+</form>
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.java
new file mode 100644
index 000000000000..83a3458576b6
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.java
@@ -0,0 +1,59 @@
+package org.jetbrains.plugins.coursecreator.ui;
+
+import com.intellij.facet.ui.FacetValidatorsManager;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.DocumentAdapter;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+
+public class CCNewProjectPanel {
+ private JPanel myPanel;
+ private JTextArea myDescription;
+ private JTextField myName;
+ private JTextField myAuthorField;
+ private FacetValidatorsManager myValidationManager;
+
+
+ public CCNewProjectPanel() {
+ final String userName = System.getProperty("user.name");
+ if (userName != null) {
+ myAuthorField.setText(userName);
+ }
+ myName.getDocument().addDocumentListener(new MyValidator());
+ myDescription.getDocument().addDocumentListener(new MyValidator());
+ myAuthorField.getDocument().addDocumentListener(new MyValidator());
+ }
+
+ public JPanel getMainPanel() {
+ return myPanel;
+ }
+
+ @NotNull
+ public String getName() {
+ return StringUtil.notNullize(myName.getText());
+ }
+
+ @NotNull
+ public String getDescription() {
+ return StringUtil.notNullize(myDescription.getText());
+ }
+
+ @NotNull
+ public String getAuthor() {
+ return StringUtil.notNullize(myAuthorField.getText());
+ }
+
+ public void registerValidators(FacetValidatorsManager manager) {
+ myValidationManager = manager;
+ }
+
+ private class MyValidator extends DocumentAdapter {
+
+ @Override
+ protected void textChanged(DocumentEvent e) {
+ myValidationManager.validate();
+ }
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchiveDialog.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchiveDialog.java
new file mode 100644
index 000000000000..d6c3bdb24af3
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchiveDialog.java
@@ -0,0 +1,40 @@
+package org.jetbrains.plugins.coursecreator.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.actions.CreateCourseArchive;
+
+import javax.swing.*;
+
+public class CreateCourseArchiveDialog extends DialogWrapper {
+
+ private CreateCourseArchivePanel myPanel;
+ private CreateCourseArchive myAction;
+
+ public CreateCourseArchiveDialog(@NotNull final Project project, CreateCourseArchive action) {
+ super(project);
+ setTitle("Create Course Archive");
+ myPanel = new CreateCourseArchivePanel(project, this);
+ myAction = action;
+ init();
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return myPanel;
+ }
+
+ public void enableOKAction(boolean isEnabled) {
+ myOKAction.setEnabled(isEnabled);
+ }
+
+ @Override
+ protected void doOKAction() {
+ myAction.setZipName(myPanel.getZipName());
+ myAction.setLocationDir(myPanel.getLocationPath());
+ super.doOKAction();
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form
new file mode 100644
index 000000000000..920dcb9494a7
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.coursecreator.ui.CreateCourseArchivePanel">
+ <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="500" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="a3b77" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="786af" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Name:"/>
+ </properties>
+ </component>
+ <component id="160bb" class="javax.swing.JTextField" binding="myNameField" default-binding="true">
+ <constraints>
+ <grid row="0" column="1" row-span="2" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="628ab" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="2" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Location:"/>
+ </properties>
+ </component>
+ <component id="aab8" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myLocationField">
+ <constraints>
+ <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <grid id="29be7" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="4fa15" class="javax.swing.JLabel" binding="myErrorIcon">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value=""/>
+ </properties>
+ </component>
+ <component id="4bdcf" class="javax.swing.JLabel" binding="myErrorLabel">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value=""/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ </children>
+ </grid>
+ </children>
+ </grid>
+</form>
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.java
new file mode 100644
index 000000000000..4e7deedf7e47
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.java
@@ -0,0 +1,65 @@
+package org.jetbrains.plugins.coursecreator.ui;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+
+public class CreateCourseArchivePanel extends JPanel {
+ private JPanel myPanel;
+ private JTextField myNameField;
+ private TextFieldWithBrowseButton myLocationField;
+ private JLabel myErrorIcon;
+ private JLabel myErrorLabel;
+ private CreateCourseArchiveDialog myDlg;
+
+ public CreateCourseArchivePanel(@NotNull final Project project, CreateCourseArchiveDialog dlg) {
+ setLayout(new BorderLayout());
+ add(myPanel, BorderLayout.CENTER);
+ myErrorIcon.setIcon(AllIcons.Actions.Lightning);
+ setState(false);
+ myDlg = dlg;
+ myNameField.setText("course");
+ myLocationField.setText(project.getBasePath());
+ FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
+ myLocationField.addBrowseFolderListener("Choose location folder", null, project, descriptor);
+ myLocationField.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String location = myLocationField.getText();
+ File file = new File(location);
+ if (!file.exists() || !file.isDirectory()) {
+ myDlg.enableOKAction(false);
+ setError("Invalid location");
+ }
+ myDlg.enableOKAction(true);
+ }
+ });
+ }
+
+ private void setState(boolean isVisible) {
+ myErrorIcon.setVisible(isVisible);
+ myErrorLabel.setVisible(isVisible);
+ }
+
+ private void setError(String message) {
+ myErrorLabel.setText(message);
+ setState(true);
+ }
+
+ public String getZipName() {
+ return myNameField.getText();
+ }
+
+ public String getLocationPath() {
+ return myLocationField.getText();
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java
new file mode 100644
index 000000000000..c7e8f715672c
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java
@@ -0,0 +1,153 @@
+package org.jetbrains.plugins.coursecreator.ui;
+
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.TaskWindow;
+
+import javax.swing.*;
+import java.io.*;
+
+public class CreateTaskWindowDialog extends DialogWrapper {
+
+ public static final String TITLE = "New Task Window";
+ private static final Logger LOG = Logger.getInstance(CreateTaskWindowDialog.class.getName());
+ private final TaskWindow myTaskWindow;
+ private final CreateTaskWindowPanel myPanel;
+ private final Project myProject;
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public CreateTaskWindowDialog(@NotNull final Project project, @NotNull final TaskWindow taskWindow, int lessonIndex,
+ int taskIndex, String taskFileName, int taskWindowIndex) {
+ super(project, true);
+ setTitle(TITLE);
+ myTaskWindow = taskWindow;
+ myPanel = new CreateTaskWindowPanel(this);
+ String generatedHintName = "lesson" + lessonIndex + "task" + taskIndex + taskFileName + "_" + taskWindowIndex;
+ myPanel.setGeneratedHintName(generatedHintName);
+ if (taskWindow.getHintName() != null) {
+ setHintText(project, taskWindow);
+ }
+ myProject = project;
+ String taskWindowTaskText = taskWindow.getTaskText();
+ myPanel.setTaskWindowText(taskWindowTaskText != null ? taskWindowTaskText : "");
+ String hintName = taskWindow.getHintName();
+ myPanel.setHintName(hintName != null ? hintName : "");
+ init();
+ initValidation();
+ }
+
+ private void setHintText(Project project, TaskWindow taskWindow) {
+ VirtualFile hints = project.getBaseDir().findChild("hints");
+ if (hints != null) {
+ File file = new File(hints.getPath(), taskWindow.getHintName());
+ StringBuilder hintText = new StringBuilder();
+ if (file.exists()) {
+ try {
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ hintText.append(line).append("\n");
+ }
+ myPanel.doClick();
+ //myPanel.enableHint(true);
+ myPanel.setHintText(hintText.toString());
+ }
+ catch (FileNotFoundException e) {
+ LOG.error("created hint was not found", e);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void doOKAction() {
+ String taskWindowText = myPanel.getTaskWindowText();
+ myTaskWindow.setTaskText(StringUtil.notNullize(taskWindowText));
+ if (myPanel.createHint()) {
+ String hintName = myPanel.getHintName();
+ myTaskWindow.setHint(hintName);
+ String hintText = myPanel.getHintText();
+ createHint(hintName, hintText);
+ }
+ super.doOKAction();
+ }
+
+ private void createHint(String hintName, String hintText) {
+ VirtualFile hintsDir = myProject.getBaseDir().findChild("hints");
+ if (hintsDir != null) {
+ File hintFile = new File(hintsDir.getPath(), hintName);
+ PrintWriter printWriter = null;
+ try {
+ printWriter = new PrintWriter(hintFile);
+ printWriter.print(hintText);
+ }
+ catch (FileNotFoundException e) {
+ //TODO:show error in UI
+ return;
+ }
+ finally {
+ if (printWriter != null) {
+ printWriter.close();
+ }
+ }
+ }
+ VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+ ProjectView.getInstance(myProject).refresh();
+ }
+
+ public void deleteHint() {
+ VirtualFile hintsDir = myProject.getBaseDir().findChild("hints");
+ if (hintsDir != null) {
+ String hintName = myTaskWindow.getHintName();
+ if (hintName == null) {
+ return;
+ }
+ File hintFile = new File(hintsDir.getPath(), hintName);
+ if (hintFile.exists()) {
+ CCProjectService.deleteProjectFile(hintFile, myProject);
+ myTaskWindow.setHint(null);
+ myPanel.resetHint();
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return myPanel;
+ }
+
+ @Nullable
+ @Override
+ public ValidationInfo doValidate() {
+ String name = myPanel.getHintName();
+ VirtualFile hintsDir = myProject.getBaseDir().findChild("hints");
+ if (hintsDir == null) {
+ return null;
+ }
+ VirtualFile child = hintsDir.findChild(name);
+ if (child == null) {
+ return null;
+ }
+ return myTaskWindow.getHintName() != null ? null : new ValidationInfo("Hint file with such filename already exists");
+ }
+
+ public void validateInput() {
+ super.initValidation();
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.form b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.form
new file mode 100644
index 000000000000..ccd91e4154b0
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.form
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.coursecreator.ui.CreateTaskWindowPanel">
+ <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="500" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="aaa28" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <labelFor value="b712"/>
+ <text value="Text:"/>
+ </properties>
+ </component>
+ <component id="d2e2f" class="javax.swing.JLabel" binding="myHintNameLabel">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <enabled value="true"/>
+ <labelFor value="ddeb1"/>
+ <text value="Hint name:"/>
+ </properties>
+ </component>
+ <component id="ddeb1" class="javax.swing.JTextField" binding="myHintName">
+ <constraints>
+ <grid row="2" column="1" row-span="1" col-span="3" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <text value=""/>
+ </properties>
+ </component>
+ <component id="d322a" class="javax.swing.JLabel" binding="myHintTextLabel">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <labelFor value="d0efc"/>
+ <text value="Hint text:"/>
+ </properties>
+ </component>
+ <grid id="51a63" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="3" column="1" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="line">
+ <color color="-6709600"/>
+ </border>
+ <children>
+ <component id="d0efc" class="javax.swing.JTextArea" binding="myHintText">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
+ <preferred-size width="300" height="100"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ <grid id="cbc70" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
+ <minimum-size width="150" height="-1"/>
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ <border type="line">
+ <color color="-6709600"/>
+ </border>
+ <children>
+ <component id="b712" class="javax.swing.JTextArea" binding="myTaskWindowText">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
+ <preferred-size width="300" height="100"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ <component id="f86b4" class="javax.swing.JCheckBox" binding="myCreateHintCheckBox" default-binding="true">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Create hint"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+</form>
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java
new file mode 100644
index 000000000000..21a7eb063c63
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java
@@ -0,0 +1,96 @@
+package org.jetbrains.plugins.coursecreator.ui;
+
+import com.intellij.ui.DocumentAdapter;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+public class CreateTaskWindowPanel extends JPanel {
+
+ private final CreateTaskWindowDialog myDialog;
+ private JPanel myPanel;
+ private JTextArea myTaskWindowText;
+ private JTextField myHintName;
+ private JTextArea myHintText;
+ private JCheckBox myCreateHintCheckBox;
+ private JLabel myHintNameLabel;
+ private JLabel myHintTextLabel;
+ private String myGeneratedHintName = "";
+
+ public CreateTaskWindowPanel(CreateTaskWindowDialog dialog) {
+ super(new BorderLayout());
+ add(myPanel, BorderLayout.CENTER);
+ myDialog = dialog;
+ enableHint(false);
+ myCreateHintCheckBox.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ int state = e.getStateChange();
+ // 1 for checked
+ enableHint(state == 1);
+ if (state == 2) {
+ myDialog.deleteHint();
+ }
+ }
+ });
+
+ myHintName.getDocument().addDocumentListener(new DocumentAdapter() {
+ @Override
+ protected void textChanged(DocumentEvent e) {
+ myDialog.validateInput();
+ }
+ });
+ }
+
+ public void enableHint(boolean isEnable) {
+ myHintName.setEnabled(isEnable);
+ myHintText.setEnabled(isEnable);
+ myHintNameLabel.setEnabled(isEnable);
+ myHintTextLabel.setEnabled(isEnable);
+ myHintName.setText(myGeneratedHintName);
+ }
+
+ public void setTaskWindowText(String taskWindowText) {
+ myTaskWindowText.setText(taskWindowText);
+ }
+
+ public void setHintName(String hintName) {
+ myHintName.setText(hintName);
+ }
+
+ public void setHintText(String hintText) {
+ myHintText.setText(hintText);
+ }
+
+ public String getTaskWindowText() {
+ return myTaskWindowText.getText();
+ }
+
+ public String getHintName() {
+ return myHintName.getText();
+ }
+
+ public String getHintText() {
+ return myHintText.getText();
+ }
+
+ public boolean createHint() {
+ return myHintName.isEnabled();
+ }
+
+ public void doClick() {
+ myCreateHintCheckBox.doClick();
+ }
+
+ public void resetHint() {
+ myHintName.setText("");
+ myHintText.setText("");
+ }
+
+ public void setGeneratedHintName(String generatedHintName) {
+ myGeneratedHintName = generatedHintName;
+ }
+}