added ability to move tasks and lessons (final part of EDU-331)
authorliana.bakradze <liana.bakradze@jetbrains.com>
Mon, 17 Aug 2015 19:52:32 +0000 (22:52 +0300)
committerliana.bakradze <liana.bakradze@jetbrains.com>
Mon, 17 Aug 2015 19:52:32 +0000 (22:52 +0300)
python/educational/course-creator/resources/META-INF/plugin.xml
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCLessonMoveHandlerDelegate.java [new file with mode: 0644]
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCTaskMoveHandlerDelegate.java [new file with mode: 0644]
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/ui/CCMoveStudyItemDialog.java [new file with mode: 0644]
python/educational/src/com/jetbrains/edu/EduUtils.java
python/educational/src/com/jetbrains/edu/courseFormat/Course.java
python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java

index 721ad70e1098ca35d17b0781124f70808e211672..fb9c60ba782b5289f069aaa2e93b3046f5d2887d 100644 (file)
@@ -26,6 +26,8 @@
     <errorHandler implementation="com.intellij.diagnostic.ITNReporter"/>
     <renameHandler implementation="com.jetbrains.edu.coursecreator.CCRenameHandler"/>
     <renameInputValidator implementation="com.jetbrains.edu.coursecreator.CCRenameInputValidator"/>
+    <refactoring.moveHandler implementation="com.jetbrains.edu.coursecreator.CCLessonMoveHandlerDelegate" order="first"/>
+    <refactoring.moveHandler implementation="com.jetbrains.edu.coursecreator.CCTaskMoveHandlerDelegate" order="first"/>
   </extensions>
 
   <application-components>
diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCLessonMoveHandlerDelegate.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCLessonMoveHandlerDelegate.java
new file mode 100644 (file)
index 0000000..02ac5f1
--- /dev/null
@@ -0,0 +1,134 @@
+package com.jetbrains.edu.coursecreator;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+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.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.refactoring.move.MoveCallback;
+import com.intellij.refactoring.move.MoveHandlerDelegate;
+import com.intellij.util.Function;
+import com.jetbrains.edu.EduNames;
+import com.jetbrains.edu.EduUtils;
+import com.jetbrains.edu.courseFormat.Course;
+import com.jetbrains.edu.courseFormat.Lesson;
+import com.jetbrains.edu.courseFormat.StudyItem;
+import com.jetbrains.edu.coursecreator.ui.CCMoveStudyItemDialog;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.Collections;
+
+public class CCLessonMoveHandlerDelegate extends MoveHandlerDelegate {
+
+  private static final Logger LOG = Logger.getInstance(CCLessonMoveHandlerDelegate.class);
+
+  @Override
+  public boolean canMove(DataContext dataContext) {
+    if (CommonDataKeys.PSI_FILE.getData(dataContext) != null) {
+      return false;
+    }
+    IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext);
+    if (view == null) {
+      return false;
+    }
+    PsiDirectory sourceDirectory = DirectoryChooserUtil.getOrChooseDirectory(view);
+    return CCUtils.isLessonDir(sourceDirectory);
+  }
+
+  @Override
+  public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
+    if (elements.length > 0 && elements[0] instanceof PsiDirectory) {
+      return CCUtils.isLessonDir(((PsiDirectory)elements[0]));
+    }
+    return false;
+  }
+
+  @Override
+  public boolean isValidTarget(PsiElement psiElement, PsiElement[] sources) {
+    return true;
+  }
+
+  @Override
+  public void doMove(final Project project,
+                     PsiElement[] elements,
+                     @Nullable PsiElement targetContainer,
+                     @Nullable MoveCallback callback) {
+    if (targetContainer == null || !(targetContainer instanceof PsiDirectory)) {
+      return;
+    }
+    final Course course = CCProjectService.getInstance(project).getCourse();
+    final PsiDirectory sourceDirectory = (PsiDirectory)elements[0];
+    final Lesson sourceLesson = course.getLesson(sourceDirectory.getName());
+    final Lesson targetLesson = course.getLesson(((PsiDirectory)targetContainer).getName());
+    if (targetLesson == null) {
+      Messages.showInfoMessage("Lessons can be moved only to other lessons", "Incorrect Target For Move");
+      return;
+    }
+    final CCMoveStudyItemDialog dialog = new CCMoveStudyItemDialog(project, EduNames.LESSON, targetLesson.getName());
+    dialog.show();
+    if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+      return;
+    }
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          sourceDirectory.getVirtualFile().rename(this, "tmp");
+        }
+        catch (IOException e) {
+          LOG.error(e);
+        }
+      }
+    });
+    final VirtualFile[] lessonDirs = project.getBaseDir().getChildren();
+    final Function<VirtualFile, StudyItem> getStudyItem = new Function<VirtualFile, StudyItem>() {
+      @Override
+      public StudyItem fun(VirtualFile file) {
+        return course.getLesson(file.getName());
+      }
+    };
+
+    int sourceLessonIndex = sourceLesson.getIndex();
+    sourceLesson.setIndex(-1);
+    CCUtils.updateHigherElements(lessonDirs, getStudyItem, sourceLessonIndex, EduNames.LESSON, -1);
+
+    final int newItemIndex = targetLesson.getIndex() + dialog.getIndexDelta();
+
+    CCUtils.updateHigherElements(lessonDirs, getStudyItem,
+                                 newItemIndex - 1, EduNames.LESSON, 1);
+
+    sourceLesson.setIndex(newItemIndex);
+    Collections.sort(course.getLessons(), EduUtils.INDEX_COMPARATOR);
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          sourceDirectory.getVirtualFile().rename(this, EduNames.LESSON + newItemIndex);
+        }
+        catch (IOException e) {
+          LOG.error(e);
+        }
+      }
+    });
+  }
+
+  @Override
+  public boolean tryToMove(PsiElement element,
+                           Project project,
+                           DataContext dataContext,
+                           @Nullable PsiReference reference,
+                           Editor editor) {
+    return true;
+  }
+}
diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCTaskMoveHandlerDelegate.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCTaskMoveHandlerDelegate.java
new file mode 100644 (file)
index 0000000..818b71c
--- /dev/null
@@ -0,0 +1,197 @@
+package com.jetbrains.edu.coursecreator;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+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.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.refactoring.move.MoveCallback;
+import com.intellij.refactoring.move.MoveHandlerDelegate;
+import com.intellij.util.Function;
+import com.jetbrains.edu.EduNames;
+import com.jetbrains.edu.EduUtils;
+import com.jetbrains.edu.courseFormat.Course;
+import com.jetbrains.edu.courseFormat.Lesson;
+import com.jetbrains.edu.courseFormat.StudyItem;
+import com.jetbrains.edu.courseFormat.Task;
+import com.jetbrains.edu.coursecreator.ui.CCMoveStudyItemDialog;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+public class CCTaskMoveHandlerDelegate extends MoveHandlerDelegate {
+
+  private static final Logger LOG = Logger.getInstance(CCTaskMoveHandlerDelegate.class);
+  @Override
+  public boolean canMove(DataContext dataContext) {
+    if (CommonDataKeys.PSI_FILE.getData(dataContext) != null) {
+      return false;
+    }
+    IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext);
+    if (view == null) {
+      return false;
+    }
+    PsiDirectory sourceDirectory = DirectoryChooserUtil.getOrChooseDirectory(view);
+    return isTaskDir(sourceDirectory);
+  }
+
+  @Override
+  public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
+    if (elements.length > 0 && elements[0] instanceof PsiDirectory) {
+      return isTaskDir(((PsiDirectory)elements[0]));
+    }
+    return false;
+  }
+
+  private static boolean isTaskDir(PsiDirectory sourceDirectory) {
+    if (sourceDirectory == null) {
+      return false;
+    }
+    CCProjectService service = CCProjectService.getInstance(sourceDirectory.getProject());
+    Course course = service.getCourse();
+    if (course == null) {
+      return false;
+    }
+    return EduUtils.getTask(sourceDirectory, course) != null;
+  }
+
+  @Override
+  public boolean isValidTarget(PsiElement psiElement, PsiElement[] sources) {
+    return true;
+  }
+
+  @Override
+  public void doMove(final Project project,
+                     PsiElement[] elements,
+                     @Nullable PsiElement targetContainer,
+                     @Nullable MoveCallback callback) {
+    if (targetContainer == null || !(targetContainer instanceof PsiDirectory)) {
+      return;
+    }
+
+    PsiDirectory targetDir = (PsiDirectory)targetContainer;
+    if (!isTaskDir(targetDir) && !CCUtils.isLessonDir(targetDir)) {
+      Messages.showInfoMessage("Tasks can be moved only to other lessons or inside lesson", "Incorrect Target For Move");
+      return;
+    }
+    final Course course = CCProjectService.getInstance(project).getCourse();
+    final PsiDirectory sourceDirectory = (PsiDirectory)elements[0];
+
+    final Task taskToMove = EduUtils.getTask(sourceDirectory, course);
+    if (taskToMove == null) {
+      return;
+    }
+
+    if (CCUtils.isLessonDir(targetDir)) {
+      //if user moves task to any lesson, this task is inserted as the last task in this lesson
+      Lesson targetLesson = course.getLesson(targetDir.getName());
+      if (targetLesson == null) {
+        return;
+      }
+      List<Task> taskList = targetLesson.getTaskList();
+      moveTask(sourceDirectory, taskToMove, taskList.isEmpty() ? null : taskList.get(taskList.size() - 1),
+               1, targetDir.getVirtualFile(), targetLesson);
+    }
+    else {
+      PsiDirectory lessonDir = targetDir.getParent();
+      if (lessonDir == null) {
+        return;
+      }
+      Task targetTask = EduUtils.getTask(targetDir, course);
+      if (targetTask == null) {
+        return;
+      }
+      final CCMoveStudyItemDialog dialog = new CCMoveStudyItemDialog(project, EduNames.TASK, targetTask.getName());
+      dialog.show();
+      if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+        return;
+      }
+      moveTask(sourceDirectory, taskToMove, targetTask, dialog.getIndexDelta(),
+               lessonDir.getVirtualFile(), targetTask.getLesson());
+    }
+
+  }
+
+  private void moveTask(final PsiDirectory sourceDirectory,
+                        final Task taskToMove,
+                        Task targetTask,
+                        int indexDelta,
+                        final VirtualFile targetDirectory,
+                        Lesson targetLesson) {
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          sourceDirectory.getVirtualFile().rename(this, "tmp");
+        }
+        catch (IOException e) {
+          LOG.error(e);
+        }
+      }
+    });
+    final VirtualFile sourceLessonDir = sourceDirectory.getVirtualFile().getParent();
+    if (sourceLessonDir == null) {
+      return;
+    }
+    CCUtils.updateHigherElements(sourceLessonDir.getChildren(), new Function<VirtualFile, StudyItem>() {
+      @Override
+      public StudyItem fun(VirtualFile file) {
+        return taskToMove.getLesson().getTask(file.getName());
+      }
+    }, taskToMove.getIndex(), EduNames.TASK, -1);
+
+    final int newItemIndex = targetTask != null ? targetTask.getIndex() + indexDelta : 1;
+    taskToMove.setIndex(-1);
+    taskToMove.getLesson().getTaskList().remove(taskToMove);
+    final Lesson finalTargetLesson = targetLesson;
+    CCUtils.updateHigherElements(targetDirectory.getChildren(), new Function<VirtualFile, StudyItem>() {
+                                   @Override
+                                   public StudyItem fun(VirtualFile file) {
+                                     return finalTargetLesson.getTask(file.getName());
+                                   }
+                                 },
+                                 newItemIndex - 1, EduNames.TASK, 1);
+
+    taskToMove.setIndex(newItemIndex);
+    taskToMove.setLesson(targetLesson);
+    targetLesson.getTaskList().add(taskToMove);
+    Collections.sort(targetLesson.getTaskList(), EduUtils.INDEX_COMPARATOR);
+
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          //moving file to the same directory leads to exception
+          if (!targetDirectory.equals(sourceLessonDir)) {
+            sourceDirectory.getVirtualFile().move(this, targetDirectory);
+          }
+          sourceDirectory.getVirtualFile().rename(this, EduNames.TASK + newItemIndex);
+        }
+        catch (IOException e) {
+          LOG.error(e);
+        }
+      }
+    });
+  }
+
+  @Override
+  public boolean tryToMove(PsiElement element,
+                           Project project,
+                           DataContext dataContext,
+                           @Nullable PsiReference reference,
+                           Editor editor) {
+    return true;
+  }
+}
index 650e43fb3220dff3706e0989aca6b8f5e83b27fe..36fda69ede933efd1651970597efbc5107e4e83e 100644 (file)
@@ -6,6 +6,7 @@ import com.intellij.lang.Language;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.util.Function;
@@ -90,4 +91,16 @@ public class CCUtils {
       });
     }
   }
+
+  public static boolean isLessonDir(PsiDirectory sourceDirectory) {
+    if (sourceDirectory == null) {
+      return false;
+    }
+    CCProjectService service = CCProjectService.getInstance(sourceDirectory.getProject());
+    Course course = service.getCourse();
+    if (course != null && course.getLesson(sourceDirectory.getName()) != null) {
+      return true;
+    }
+    return false;
+  }
 }
index ddbaba2ada1bcf19f2a6bc6301ceb1fd6dc02c88..523caf9fa84ea9341b9f23a4509bb81bd94660b1 100644 (file)
@@ -16,6 +16,7 @@ import com.intellij.psi.PsiElement;
 import com.intellij.util.Function;
 import com.intellij.util.PlatformIcons;
 import com.jetbrains.edu.EduNames;
+import com.jetbrains.edu.EduUtils;
 import com.jetbrains.edu.courseFormat.Course;
 import com.jetbrains.edu.courseFormat.Lesson;
 import com.jetbrains.edu.courseFormat.StudyItem;
@@ -138,15 +139,7 @@ public class CCCreateTask extends CCCreateStudyItemActionBase {
   @Nullable
   @Override
   protected StudyItem getThresholdItem(@NotNull Course course, @NotNull PsiDirectory sourceDirectory) {
-    PsiDirectory parent = sourceDirectory.getParent();
-    if (parent == null) {
-      return null;
-    }
-    Lesson lesson = course.getLesson(parent.getName());
-    if (lesson == null) {
-      return null;
-    }
-    return lesson.getTask(sourceDirectory.getName());
+    return EduUtils.getTask(sourceDirectory, course);
   }
 
   @Override
index adf934b325b52495cc2100c8364b1b07c4e19961..fd4854ffb359c84bbdb960d09c40be3b6d275a20 100644 (file)
@@ -9,18 +9,16 @@ public class CCCreateStudyItemPanel extends JPanel {
   private JTextField myNameField;
   private CCItemPositionPanel myPositionalPanel;
   private String myThresholdName;
-  private int myThresholdIndex;
 
   public CCCreateStudyItemPanel(String itemName, String thresholdName, int thresholdIndex) {
     myThresholdName = thresholdName;
-    myThresholdIndex = thresholdIndex;
     myItemName = itemName;
     myNameField.setText(itemName + thresholdIndex);
     add(myPanel, BorderLayout.CENTER);
   }
 
   private void createUIComponents() {
-    myPositionalPanel = new CCItemPositionPanel(myItemName, myThresholdName, myThresholdIndex);
+    myPositionalPanel = new CCItemPositionPanel(myItemName, myThresholdName);
   }
 
   public String getItemName() {
index 72fa3666b9ee210858e8cf03e87c58bb052fd4d2..2b15aa9b85b4bc20dca063250f0564383c4d9ba8 100644 (file)
@@ -1,5 +1,6 @@
 package com.jetbrains.edu.coursecreator.ui;
 
+import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.ui.components.JBLabel;
 import com.intellij.ui.components.JBRadioButton;
 
@@ -12,10 +13,10 @@ public class CCItemPositionPanel extends JPanel {
   private JBRadioButton myAfterButton;
   private JBLabel mySpecifyPositionLabel;
 
-  public CCItemPositionPanel(String itemName, String thresholdName, int thresholdNum) {
+  public CCItemPositionPanel(String itemName, String thresholdName) {
     this.add(myPanel, BorderLayout.CENTER);
-    mySpecifyPositionLabel.setText("Specify " + itemName + " position:");
-    String postfix = itemName + " " + thresholdNum + " '" + thresholdName + "'";
+    mySpecifyPositionLabel.setText(StringUtil.toTitleCase(itemName) + " position:");
+    String postfix = "'" + thresholdName + "'";
     ButtonGroup group = new ButtonGroup();
     group.add(myBeforeButton);
     group.add(myAfterButton);
diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/ui/CCMoveStudyItemDialog.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/ui/CCMoveStudyItemDialog.java
new file mode 100644 (file)
index 0000000..a82fa33
--- /dev/null
@@ -0,0 +1,29 @@
+package com.jetbrains.edu.coursecreator.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class CCMoveStudyItemDialog extends DialogWrapper {
+  private final CCItemPositionPanel myPanel;
+
+  public CCMoveStudyItemDialog(@Nullable Project project, String itemName, String thresholdName) {
+    super(project);
+    myPanel = new CCItemPositionPanel(itemName, thresholdName);
+    setTitle("Move " + StringUtil.toTitleCase(itemName));
+    init();
+  }
+
+  @Nullable
+  @Override
+  protected JComponent createCenterPanel() {
+    return myPanel;
+  }
+
+  public int getIndexDelta() {
+    return myPanel.getIndexDelta();
+  }
+}
index ec743b0eba9fe82ef72bbd80feb444469e889f2b..782c8ca7989f82ab9897744036add0b8123b3dd0 100644 (file)
@@ -12,10 +12,8 @@ import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileManager;
-import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
-import com.jetbrains.edu.courseFormat.StudyItem;
-import com.jetbrains.edu.courseFormat.Task;
-import com.jetbrains.edu.courseFormat.TaskFile;
+import com.intellij.psi.PsiDirectory;
+import com.jetbrains.edu.courseFormat.*;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -240,4 +238,17 @@ public class EduUtils {
       });
     }
   }
+
+  @Nullable
+  public static Task getTask(@NotNull final PsiDirectory directory, @NotNull final Course course) {
+    PsiDirectory lessonDir = directory.getParent();
+    if (lessonDir == null) {
+      return null;
+    }
+    Lesson lesson = course.getLesson(lessonDir.getName());
+    if (lesson == null) {
+      return null;
+    }
+    return lesson.getTask(directory.getName());
+  }
 }
index 6f4b07c2feb5a2d854baead1c6de6f92cb085ede..b7e995d4563895f8a8023a55ebab758f9f80365c 100644 (file)
@@ -58,7 +58,12 @@ public class Course {
     if (!EduUtils.indexIsValid(lessonIndex, lessons)) {
       return null;
     }
-    return lessons.get(lessonIndex);
+    for (Lesson lesson : lessons) {
+      if (lesson.getIndex() - 1 == lessonIndex) {
+        return lesson;
+      }
+    }
+    return null;
   }
 
   @NotNull
index dd46f939f077f4ffa36b73f6fb847b4aa250984d..edb7113a864141af4a0c06a53ee1fb74f44f936a 100644 (file)
@@ -79,7 +79,12 @@ public class Lesson implements StudyItem {
     if (!EduUtils.indexIsValid(index, tasks)) {
       return null;
     }
-    return tasks.get(index);
+    for (Task task : tasks) {
+      if (task.getIndex() - 1 == index) {
+        return task;
+      }
+    }
+    return null;
   }
 
 }