blob: c46c4f56f93759fad7a8c3cf2bcda75e92dc5efe [file] [log] [blame]
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;
boolean coveredByPrevTW = false;
int prevIndex = w.getIndex() - 1;
if (StudyUtils.indexIsValid(prevIndex, taskWindows)) {
TaskWindow prevTW = taskWindows.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);
}
}
}
}
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);
navigateToTaskWindow(editor, firstTaskWindow);
}
}
private void navigateToTaskWindow(@NotNull final Editor editor, @NotNull final TaskWindow firstTaskWindow) {
if (!firstTaskWindow.isValid(editor.getDocument())) {
return;
}
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);
}
public void navigateToFirstFailedTaskWindow(@NotNull final Editor editor) {
for (TaskWindow taskWindow : taskWindows) {
if (taskWindow.getStatus() != StudyStatus.Failed) {
continue;
}
navigateToTaskWindow(editor, taskWindow);
break;
}
}
public boolean hasFailedTaskWindows() {
return taskWindows.size() > 0 && getStatus() == StudyStatus.Failed;
}
}