diff options
Diffstat (limited to 'python/edu/course-creator/src')
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; + } +} |