diff options
Diffstat (limited to 'python/edu/learn-python/src/com/jetbrains/python/edu/course')
10 files changed, 986 insertions, 0 deletions
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Course.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Course.java new file mode 100644 index 000000000000..89613ac7918f --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Course.java @@ -0,0 +1,104 @@ +package com.jetbrains.python.edu.course; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class Course { + + private static final Logger LOG = Logger.getInstance(Course.class.getName()); + public static final String PLAYGROUND_DIR = "Playground"; + public List<Lesson> lessons = new ArrayList<Lesson>(); + public String description; + public String name; + public String myResourcePath = ""; + public String author; + public static final String COURSE_DIR = "course"; + public static final String HINTS_DIR = "hints"; + + + public List<Lesson> getLessons() { + return lessons; + } + + /** + * Initializes state of course + */ + public void init(boolean isRestarted) { + for (Lesson lesson : lessons) { + lesson.init(this, isRestarted); + } + } + + public String getAuthor() { + return author; + } + + /** + * Creates course directory in project user created + * + * @param baseDir project directory + * @param resourceRoot directory where original course is stored + */ + public void create(@NotNull final VirtualFile baseDir, @NotNull final File resourceRoot) { + ApplicationManager.getApplication().invokeLater( + new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + for (int i = 0; i < lessons.size(); i++) { + Lesson lesson = lessons.get(i); + lesson.setIndex(i); + lesson.create(baseDir, resourceRoot); + } + baseDir.createChildDirectory(this, PLAYGROUND_DIR); + File[] files = resourceRoot.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return !name.contains(Lesson.LESSON_DIR) && !name.equals("course.json") && !name.equals("hints"); + } + }); + for (File file: files) { + FileUtil.copy(file, new File(baseDir.getPath(), file.getName())); + } + } + catch (IOException e) { + LOG.error(e); + } + } + }); + } + }); + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setResourcePath(@NotNull final String resourcePath) { + myResourcePath = resourcePath; + } + + public String getResourcePath() { + return myResourcePath; + } + + public String getDescription() { + return description; + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/CourseInfo.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/CourseInfo.java new file mode 100644 index 000000000000..9f820c12c572 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/CourseInfo.java @@ -0,0 +1,52 @@ +package com.jetbrains.python.edu.course; + +/** + * Implementation of class which contains information to be shawn in course description in tool window + * and when project is being created + */ +public class CourseInfo { + private String myName; + private String myAuthor; + private String myDescription; + public static CourseInfo INVALID_COURSE = new CourseInfo("", "", ""); + + public CourseInfo(String name, String author, String description) { + myName = name; + myAuthor = author; + myDescription = description; + } + + public String getName() { + return myName; + } + + public String getAuthor() { + return myAuthor; + } + + public String getDescription() { + return myDescription; + } + + @Override + public String toString() { + return myName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CourseInfo that = (CourseInfo)o; + return that.getName().equals(myName) && that.getAuthor().equals(myAuthor) + && that.getDescription().equals(myDescription); + } + + @Override + public int hashCode() { + int result = myName != null ? myName.hashCode() : 0; + result = 31 * result + (myAuthor != null ? myAuthor.hashCode() : 0); + result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0); + return result; + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java new file mode 100644 index 000000000000..3879d519957e --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java @@ -0,0 +1,109 @@ +package com.jetbrains.python.edu.course; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.xmlb.annotations.Transient; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class Lesson implements Stateful{ + public String name; + public List<Task> taskList = new ArrayList<Task>(); + private Course myCourse = null; + public int myIndex = -1; + public static final String LESSON_DIR = "lesson"; + public LessonInfo myLessonInfo = new LessonInfo(); + + public LessonInfo getLessonInfo() { + return myLessonInfo; + } + + @Transient + public StudyStatus getStatus() { + for (Task task : taskList) { + StudyStatus taskStatus = task.getStatus(); + if (taskStatus == StudyStatus.Unchecked || taskStatus == StudyStatus.Failed) { + return StudyStatus.Unchecked; + } + } + return StudyStatus.Solved; + } + + @Override + public void setStatus(StudyStatus status, StudyStatus oldStatus) { + for (Task task : taskList) { + task.setStatus(status, oldStatus); + } + } + + public List<Task> getTaskList() { + return taskList; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Creates lesson directory in its course folder in project user created + * + * @param courseDir project directory of course + * @param resourceRoot directory where original lesson stored + * @throws java.io.IOException + */ + public void create(@NotNull final VirtualFile courseDir, @NotNull final File resourceRoot) throws IOException { + String lessonDirName = LESSON_DIR + Integer.toString(myIndex + 1); + VirtualFile lessonDir = courseDir.createChildDirectory(this, lessonDirName); + for (int i = 0; i < taskList.size(); i++) { + Task task = taskList.get(i); + task.setIndex(i); + task.create(lessonDir, new File(resourceRoot, lessonDir.getName())); + } + } + + + /** + * Initializes state of lesson + * + * @param course course which lesson belongs to + */ + public void init(final Course course, boolean isRestarted) { + myCourse = course; + myLessonInfo.setTaskNum(taskList.size()); + myLessonInfo.setTaskUnchecked(taskList.size()); + for (Task task : taskList) { + task.init(this, isRestarted); + } + } + + public Lesson next() { + List<Lesson> lessons = myCourse.getLessons(); + if (myIndex + 1 >= lessons.size()) { + return null; + } + return lessons.get(myIndex + 1); + } + + public void setIndex(int index) { + myIndex = index; + } + + public int getIndex() { + return myIndex; + } + + public Lesson prev() { + if (myIndex - 1 < 0) { + return null; + } + return myCourse.getLessons().get(myIndex - 1); + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java new file mode 100644 index 000000000000..85e2eb8be1a9 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java @@ -0,0 +1,60 @@ +package com.jetbrains.python.edu.course; + +/** + * Implementation of class which contains information about student progress in current lesson + */ +public class LessonInfo { + private int myTaskNum; + private int myTaskFailed; + private int myTaskSolved; + private int myTaskUnchecked; + + public int getTaskNum() { + return myTaskNum; + } + + public void setTaskNum(int taskNum) { + myTaskNum = taskNum; + } + + public int getTaskFailed() { + return myTaskFailed; + } + + public void setTaskFailed(int taskFailed) { + myTaskFailed = taskFailed; + } + + public int getTaskSolved() { + return myTaskSolved; + } + + public void setTaskSolved(int taskSolved) { + myTaskSolved = taskSolved; + } + + public int getTaskUnchecked() { + return myTaskUnchecked; + } + + public void setTaskUnchecked(int taskUnchecked) { + myTaskUnchecked = taskUnchecked; + } + + public void update(StudyStatus status, int delta) { + switch (status) { + case Solved: { + myTaskSolved += delta; + break; + } + case Failed: { + myTaskFailed += delta; + break; + } + case Unchecked: { + myTaskUnchecked += delta; + break; + } + } + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java new file mode 100644 index 000000000000..3a163622f56d --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java @@ -0,0 +1,6 @@ +package com.jetbrains.python.edu.course; + +public interface Stateful { + StudyStatus getStatus(); + void setStatus(StudyStatus status, StudyStatus oldStatus); +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/StudyStatus.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/StudyStatus.java new file mode 100644 index 000000000000..d95b42b73866 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/StudyStatus.java @@ -0,0 +1,8 @@ +package com.jetbrains.python.edu.course; + +/** + * @see {@link TaskWindow#myStatus} + */ +public enum StudyStatus { + Unchecked, Solved, Failed +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java new file mode 100644 index 000000000000..2323412f4374 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java @@ -0,0 +1,201 @@ +package com.jetbrains.python.edu.course; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.xmlb.annotations.Transient; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import com.jetbrains.python.edu.StudyUtils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of task which contains task files, tests, input file for tests + */ +public class Task implements Stateful{ + public static final String TASK_DIR = "task"; + private static final String ourTestFile = "tests.py"; + public String name; + private static final String ourTextFile = "task.html"; + public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>(); + private Lesson myLesson; + public int myIndex; + public List<UserTest> userTests = new ArrayList<UserTest>(); + public static final String USER_TESTS = "userTests"; + + public Map<String, TaskFile> getTaskFiles() { + return taskFiles; + } + + @Transient + public StudyStatus getStatus() { + for (TaskFile taskFile : taskFiles.values()) { + StudyStatus taskFileStatus = taskFile.getStatus(); + if (taskFileStatus == StudyStatus.Unchecked) { + return StudyStatus.Unchecked; + } + if (taskFileStatus == StudyStatus.Failed) { + return StudyStatus.Failed; + } + } + return StudyStatus.Solved; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) { + LessonInfo lessonInfo = myLesson.getLessonInfo(); + if (status != oldStatus) { + lessonInfo.update(oldStatus, -1); + lessonInfo.update(status, +1); + } + for (TaskFile taskFile : taskFiles.values()) { + taskFile.setStatus(status, oldStatus); + } + } + + public List<UserTest> getUserTests() { + return userTests; + } + + public String getTestFile() { + return ourTestFile; + } + + public String getText() { + return ourTextFile; + } + + /** + * Creates task directory in its lesson folder in project user created + * + * @param lessonDir project directory of lesson which task belongs to + * @param resourceRoot directory where original task file stored + * @throws java.io.IOException + */ + public void create(@NotNull final VirtualFile lessonDir, @NotNull final File resourceRoot) throws IOException { + VirtualFile taskDir = lessonDir.createChildDirectory(this, TASK_DIR + Integer.toString(myIndex + 1)); + File newResourceRoot = new File(resourceRoot, taskDir.getName()); + int i = 0; + for (Map.Entry<String, TaskFile> taskFile : taskFiles.entrySet()) { + TaskFile taskFileContent = taskFile.getValue(); + taskFileContent.setIndex(i); + i++; + taskFileContent.create(taskDir, newResourceRoot, taskFile.getKey()); + } + File[] filesInTask = newResourceRoot.listFiles(); + if (filesInTask != null) { + for (File file : filesInTask) { + String fileName = file.getName(); + if (!isTaskFile(fileName)) { + File resourceFile = new File(newResourceRoot, fileName); + File fileInProject = new File(taskDir.getCanonicalPath(), fileName); + FileUtil.copy(resourceFile, fileInProject); + } + } + } + } + + private boolean isTaskFile(@NotNull final String fileName) { + return taskFiles.get(fileName) != null; + } + + @Nullable + public TaskFile getFile(@NotNull final String fileName) { + return taskFiles.get(fileName); + } + + /** + * Initializes state of task file + * + * @param lesson lesson which task belongs to + */ + public void init(final Lesson lesson, boolean isRestarted) { + myLesson = lesson; + for (TaskFile taskFile : taskFiles.values()) { + taskFile.init(this, isRestarted); + } + } + + public Task next() { + Lesson currentLesson = this.myLesson; + List<Task> taskList = myLesson.getTaskList(); + if (myIndex + 1 < taskList.size()) { + return taskList.get(myIndex + 1); + } + Lesson nextLesson = currentLesson.next(); + if (nextLesson == null) { + return null; + } + return StudyUtils.getFirst(nextLesson.getTaskList()); + } + + public Task prev() { + Lesson currentLesson = this.myLesson; + if (myIndex - 1 >= 0) { + return myLesson.getTaskList().get(myIndex - 1); + } + Lesson prevLesson = currentLesson.prev(); + if (prevLesson == null) { + return null; + } + //getting last task in previous lesson + return prevLesson.getTaskList().get(prevLesson.getTaskList().size() - 1); + } + + public void setIndex(int index) { + myIndex = index; + } + + public int getIndex() { + return myIndex; + } + + public Lesson getLesson() { + return myLesson; + } + + + @Nullable + public VirtualFile getTaskDir(Project project) { + String lessonDirName = Lesson.LESSON_DIR + String.valueOf(myLesson.getIndex() + 1); + String taskDirName = TASK_DIR + String.valueOf(myIndex + 1); + VirtualFile courseDir = project.getBaseDir(); + if (courseDir != null) { + VirtualFile lessonDir = courseDir.findChild(lessonDirName); + if (lessonDir != null) { + return lessonDir.findChild(taskDirName); + } + } + return null; + } + + /** + * Gets text of resource file such as test input file or task text in needed format + * + * @param fileName name of resource file which should exist in task directory + * @param wrapHTML if it's necessary to wrap text with html tags + * @return text of resource file wrapped with html tags if necessary + */ + @Nullable + public String getResourceText(@NotNull final Project project, @NotNull final String fileName, boolean wrapHTML) { + VirtualFile taskDir = getTaskDir(project); + if (taskDir != null) { + return StudyUtils.getFileText(taskDir.getCanonicalPath(), fileName, wrapHTML); + } + return null; + } + +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java new file mode 100644 index 000000000000..4f17fc0d27f3 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java @@ -0,0 +1,228 @@ +package com.jetbrains.python.edu.course; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.xmlb.annotations.Transient; +import com.jetbrains.python.edu.StudyUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Implementation of task file which contains task windows for student to type in and + * which is visible to student in project view + */ + +public class TaskFile implements Stateful{ + public List<TaskWindow> taskWindows = new ArrayList<TaskWindow>(); + private Task myTask; + @Transient + private TaskWindow mySelectedTaskWindow = null; + public int myIndex = -1; + private boolean myUserCreated = false; + + /** + * @return if all the windows in task file are marked as resolved + */ + @Transient + public StudyStatus getStatus() { + for (TaskWindow taskWindow : taskWindows) { + StudyStatus windowStatus = taskWindow.getStatus(); + if (windowStatus == StudyStatus.Failed) { + return StudyStatus.Failed; + } + if (windowStatus == StudyStatus.Unchecked) { + return StudyStatus.Unchecked; + } + } + return StudyStatus.Solved; + } + + public Task getTask() { + return myTask; + } + + @Nullable + @Transient + public TaskWindow getSelectedTaskWindow() { + return mySelectedTaskWindow; + } + + /** + * @param selectedTaskWindow window from this task file to be set as selected + */ + public void setSelectedTaskWindow(@NotNull final TaskWindow selectedTaskWindow) { + if (selectedTaskWindow.getTaskFile() == this) { + mySelectedTaskWindow = selectedTaskWindow; + } + else { + throw new IllegalArgumentException("Window may be set as selected only in task file which it belongs to"); + } + } + + public List<TaskWindow> getTaskWindows() { + return taskWindows; + } + + /** + * Creates task files in its task folder in project user created + * + * @param taskDir project directory of task which task file belongs to + * @param resourceRoot directory where original task file stored + * @throws java.io.IOException + */ + public void create(@NotNull final VirtualFile taskDir, @NotNull final File resourceRoot, + @NotNull final String name) throws IOException { + String systemIndependentName = FileUtil.toSystemIndependentName(name); + final int index = systemIndependentName.lastIndexOf("/"); + if (index > 0) { + systemIndependentName = systemIndependentName.substring(index + 1); + } + File resourceFile = new File(resourceRoot, name); + File fileInProject = new File(taskDir.getPath(), systemIndependentName); + FileUtil.copy(resourceFile, fileInProject); + } + + public void drawAllWindows(Editor editor) { + for (TaskWindow taskWindow : taskWindows) { + taskWindow.draw(editor, false, false); + } + } + + + /** + * @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 : taskWindows) { + if (tw.getLine() <= line) { + int twStartOffset = tw.getRealStartOffset(document); + final int length = tw.getLength() > 0 ? tw.getLength() : 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 : taskWindows) { + if (taskTaskWindow.getLine() >= startLine) { + taskTaskWindow.setLine(taskTaskWindow.getLine() + change); + } + } + } + + /** + * Initializes state of task file + * + * @param task task which task file belongs to + */ + + public void init(final Task task, boolean isRestarted) { + myTask = task; + for (TaskWindow taskWindow : taskWindows) { + taskWindow.init(this, isRestarted); + } + Collections.sort(taskWindows); + for (int i = 0; i < taskWindows.size(); i++) { + taskWindows.get(i).setIndex(i); + } + } + + /** + * @param index index of task file in list of task files of its task + */ + public void setIndex(int index) { + myIndex = index; + } + + /** + * 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 : taskWindows) { + if ((w.getLine() == line) && (w.getStart() >= oldEndOffsetInLine)) { + int distance = w.getStart() - oldEndOffsetInLine; + if (lineChange != 0 || newEndOffsetInLine <= w.getStart()) { + w.setStart(distance + newEndOffsetInLine); + w.setLine(line + lineChange); + } + } + } + } + + public static void copy(@NotNull final TaskFile source, @NotNull final TaskFile target) { + List<TaskWindow> sourceTaskWindows = source.getTaskWindows(); + List<TaskWindow> windowsCopy = new ArrayList<TaskWindow>(sourceTaskWindows.size()); + for (TaskWindow taskWindow : sourceTaskWindows) { + TaskWindow taskWindowCopy = new TaskWindow(); + taskWindowCopy.setLine(taskWindow.getLine()); + taskWindowCopy.setStart(taskWindow.getStart()); + taskWindowCopy.setLength(taskWindow.getLength()); + taskWindowCopy.setPossibleAnswer(taskWindow.getPossibleAnswer()); + taskWindowCopy.setIndex(taskWindow.getIndex()); + windowsCopy.add(taskWindowCopy); + } + target.setTaskWindows(windowsCopy); + } + + public void setTaskWindows(List<TaskWindow> taskWindows) { + this.taskWindows = taskWindows; + } + + public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) { + for (TaskWindow taskWindow : taskWindows) { + taskWindow.setStatus(status, oldStatus); + } + } + + public void setUserCreated(boolean userCreated) { + myUserCreated = userCreated; + } + + public boolean isUserCreated() { + return myUserCreated; + } + + public void navigateToFirstTaskWindow(@NotNull final Editor editor) { + if (!taskWindows.isEmpty()) { + TaskWindow firstTaskWindow = StudyUtils.getFirst(taskWindows); + mySelectedTaskWindow = firstTaskWindow; + LogicalPosition taskWindowStart = new LogicalPosition(firstTaskWindow.getLine(), firstTaskWindow.getStart()); + editor.getCaretModel().moveToLogicalPosition(taskWindowStart); + int startOffset = firstTaskWindow.getRealStartOffset(editor.getDocument()); + int endOffset = startOffset + firstTaskWindow.getLength(); + editor.getSelectionModel().setSelection(startOffset, endOffset); + } + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java new file mode 100644 index 000000000000..4fb112cc1f9b --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java @@ -0,0 +1,177 @@ +package com.jetbrains.python.edu.course; + +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.ui.JBColor; +import org.jetbrains.annotations.NotNull; + +/** + * Implementation of windows which user should type in + */ + + +public class TaskWindow implements Comparable, Stateful { + + public int line = 0; + public int start = 0; + public String hint = ""; + public String possibleAnswer = ""; + public int length = 0; + private TaskFile myTaskFile; + public int myIndex = -1; + public int myInitialLine = -1; + public int myInitialStart = -1; + public int myInitialLength = -1; + public StudyStatus myStatus = StudyStatus.Unchecked; + + public StudyStatus getStatus() { + return myStatus; + } + + public void setStatus(StudyStatus status, StudyStatus oldStatus) { + myStatus = status; + } + + public void setIndex(int index) { + myIndex = index; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public void setLine(int line) { + this.line = line; + } + + public int getLine() { + return line; + } + + + /** + * Draw task window with color according to its status + */ + public void draw(@NotNull final Editor editor, boolean drawSelection, boolean moveCaret) { + Document document = editor.getDocument(); + if (!isValid(document)) { + return; + } + TextAttributes defaultTestAttributes = + EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES); + JBColor color = getColor(); + int startOffset = document.getLineStartOffset(line) + start; + RangeHighlighter + rh = editor.getMarkupModel().addRangeHighlighter(startOffset, startOffset + length, HighlighterLayer.LAST + 1, + new TextAttributes(defaultTestAttributes.getForegroundColor(), + defaultTestAttributes.getBackgroundColor(), color, + defaultTestAttributes.getEffectType(), + defaultTestAttributes.getFontType()), + HighlighterTargetArea.EXACT_RANGE); + if (drawSelection) { + editor.getSelectionModel().setSelection(startOffset, startOffset + length); + } + if (moveCaret) { + editor.getCaretModel().moveToOffset(startOffset); + } + rh.setGreedyToLeft(true); + rh.setGreedyToRight(true); + } + + public boolean isValid(@NotNull final Document document) { + boolean isLineValid = line < document.getLineCount() && line >= 0; + if (!isLineValid) return false; + boolean isStartValid = start >= 0 && start < document.getLineEndOffset(line); + boolean isLengthValid = (getRealStartOffset(document) + length) <= document.getTextLength(); + return isLengthValid && isStartValid; + } + + private JBColor getColor() { + if (myStatus == StudyStatus.Solved) { + return JBColor.GREEN; + } + if (myStatus == StudyStatus.Failed) { + return JBColor.RED; + } + return JBColor.BLUE; + } + + public int getRealStartOffset(@NotNull final Document document) { + return document.getLineStartOffset(line) + start; + } + + /** + * Initializes window + * + * @param file task file which window belongs to + */ + public void init(final TaskFile file, boolean isRestarted) { + if (!isRestarted) { + myInitialLine = line; + myInitialLength = length; + myInitialStart = start; + } + myTaskFile = file; + } + + public TaskFile getTaskFile() { + return myTaskFile; + } + + @Override + public int compareTo(@NotNull Object o) { + TaskWindow taskWindow = (TaskWindow)o; + if (taskWindow.getTaskFile() != myTaskFile) { + throw new ClassCastException(); + } + int lineDiff = line - taskWindow.line; + if (lineDiff == 0) { + return start - taskWindow.start; + } + return lineDiff; + } + + /** + * Returns window to its initial state + */ + public void reset() { + myStatus = StudyStatus.Unchecked; + line = myInitialLine; + start = myInitialStart; + length = myInitialLength; + } + + public String getHint() { + return hint; + } + + public String getPossibleAnswer() { + return possibleAnswer; + } + + public void setPossibleAnswer(String possibleAnswer) { + this.possibleAnswer = possibleAnswer; + } + + public int getIndex() { + return myIndex; + } +}
\ No newline at end of file diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/UserTest.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/UserTest.java new file mode 100644 index 000000000000..8133e9102a14 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/UserTest.java @@ -0,0 +1,41 @@ +package com.jetbrains.python.edu.course; + +public class UserTest { + private String input; + private String output; + private StringBuilder myInputBuffer = new StringBuilder(); + private StringBuilder myOutputBuffer = new StringBuilder(); + private boolean myEditable = false; + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } + + public StringBuilder getInputBuffer() { + return myInputBuffer; + } + + public StringBuilder getOutputBuffer() { + return myOutputBuffer; + } + + public boolean isEditable() { + return myEditable; + } + + public void setEditable(boolean editable) { + myEditable = editable; + } +} |