summaryrefslogtreecommitdiff
path: root/python/edu
diff options
context:
space:
mode:
Diffstat (limited to 'python/edu')
-rw-r--r--python/edu/build/desktop.ini6
-rw-r--r--python/edu/build/idea.nsi83
-rw-r--r--python/edu/build/paths.nsi2
-rw-r--r--python/edu/build/plugin-list.txt3
-rw-r--r--python/edu/build/pycharm_edu_build.gant19
-rw-r--r--python/edu/build/python.txt2
-rw-r--r--python/edu/build/resources/logo.bmpbin0 -> 154544 bytes
-rw-r--r--python/edu/build/resources/logo.pngbin46439 -> 0 bytes
-rw-r--r--python/edu/build/upload_pythonInfo.xml32
-rw-r--r--python/edu/course-creator/course-creator.iml16
-rw-r--r--python/edu/course-creator/resources/META-INF/plugin.xml60
-rw-r--r--python/edu/course-creator/resources/fileTemplates/internal/task.html.ft4
-rw-r--r--python/edu/course-creator/resources/fileTemplates/internal/task.py.ft1
-rw-r--r--python/edu/course-creator/resources/fileTemplates/internal/test_helper.py.ft127
-rw-r--r--python/edu/course-creator/resources/fileTemplates/internal/tests.py.ft17
-rw-r--r--python/edu/course-creator/resources/icons/gutter.pngbin0 -> 500 bytes
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java80
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java58
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectGenerator.java101
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java138
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java71
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java105
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java198
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateLesson.java89
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java122
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java110
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/DeleteTaskWindow.java62
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/ShowTaskWindowText.java44
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java55
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java45
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java41
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java111
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java133
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/CCTaskLineMarkerProvider.java75
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/TaskTextGutter.java60
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java63
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java55
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.form77
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.java59
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchiveDialog.java40
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form79
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.java65
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java153
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.form102
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java96
-rw-r--r--python/edu/learn-python/gen/icons/StudyIcons.java20
-rw-r--r--python/edu/learn-python/learn-python.iml1
-rw-r--r--python/edu/learn-python/resources/META-INF/plugin.xml10
-rw-r--r--python/edu/learn-python/resources/courses/introduction_course.zipbin93360 -> 93557 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType.pngbin0 -> 1340 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType_dark.pngbin0 -> 1338 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson.pngbin0 -> 285 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson@2x.pngbin0 -> 751 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonComp@2x.pngbin0 -> 1193 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonCompl.pngbin0 -> 483 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground.pngbin0 -> 265 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground@2x.pngbin0 -> 455 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Run.pngbin1260 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task.pngbin0 -> 160 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x.pngbin0 -> 269 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x_dark.pngbin0 -> 385 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl.pngbin0 -> 369 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x.pngbin0 -> 822 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x_dark.pngbin0 -> 965 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl_dark.pngbin0 -> 481 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl.pngbin0 -> 336 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x.pngbin0 -> 738 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x_dark.pngbin0 -> 899 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl_dark.pngbin0 -> 449 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task_dark.pngbin0 -> 241 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/add.pngbin213 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/checked.pngbin606 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/failed.pngbin549 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/icon.jpgbin4389 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/next.pngbin1345 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/playground.pngbin855 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.pngbin1354 -> 437 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh.pngbin657 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh24.pngbin1569 -> 0 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.pngbin342 -> 393 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.pngbin449 -> 576 bytes
-rw-r--r--python/edu/learn-python/resources/icons/com/jetbrains/python/edu/unchecked.pngbin604 -> 0 bytes
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java4
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java3
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java5
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyState.java52
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java24
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java76
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java44
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java378
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java4
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java34
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java5
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java4
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java5
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java (renamed from python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java)15
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java103
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java49
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java48
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java67
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java96
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/highlighting/StudyVisitorFilter.java18
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java11
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form10
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java7
-rw-r--r--python/edu/main_pycharm_edu.iml1
-rw-r--r--python/edu/resources/idea/PyCharmEduApplicationInfo.xml2
-rw-r--r--python/edu/src/META-INF/PyCharmEduPlugin.xml10
-rw-r--r--python/edu/src/com/intellij/openapi/application/PyCharmEduConfigImportSettings.java12
-rw-r--r--python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java45
-rw-r--r--python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java91
-rw-r--r--python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java56
112 files changed, 3745 insertions, 424 deletions
diff --git a/python/edu/build/desktop.ini b/python/edu/build/desktop.ini
index f56d43c998bf..0ea32dec3569 100644
--- a/python/edu/build/desktop.ini
+++ b/python/edu/build/desktop.ini
@@ -28,7 +28,7 @@ Text=Choice Python version
[Field 4]
Type=RadioButton
Left=5
-Right=45
+Right=100
Top=50
Bottom=60
State=1
@@ -36,8 +36,8 @@ Text=Python 2
[Field 5]
Type=RadioButton
-Left=95
-Right=135
+Left=120
+Right=-1
Top=50
Bottom=60
State=0
diff --git a/python/edu/build/idea.nsi b/python/edu/build/idea.nsi
index d9903c94de5e..05c37577893b 100644
--- a/python/edu/build/idea.nsi
+++ b/python/edu/build/idea.nsi
@@ -23,6 +23,7 @@ RequestExecutionLevel user
;------------------------------------------------------------------------------
!include "MUI2.nsh"
!include "FileFunc.nsh"
+!include "TextFunc.nsh"
!include UAC.nsh
!include "InstallOptions.nsh"
!include StrFunc.nsh
@@ -32,6 +33,7 @@ ${UnStrStr}
${UnStrLoc}
${UnStrRep}
${StrRep}
+${StrTok}
ReserveFile "desktop.ini"
ReserveFile "DeleteSettings.ini"
@@ -700,49 +702,69 @@ command_exists:
'$INSTDIR\bin\${PRODUCT_EXE_FILE} "%1"'
FunctionEnd
+Function getPythonInfo
+ ClearErrors
+ FileOpen $3 $INSTDIR\python\python.txt r
+ IfErrors cantOpenFile ;file can not be open.
+ ;get python2 info
+ FileRead $3 $4
+ ${StrTok} $0 $4 " " "1" "1"
+ ${StrTok} $1 $4 " " "2" "1"
+ ;get python3 info
+ FileRead $3 $4
+ ${StrTok} $R0 $4 " " "1" "1"
+ ${StrTok} $R1 $4 " " "2" "1"
+ goto Done
+cantOpenFile:
+ MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is not exist. Python will not be downloaded."
+ StrCpy $0 "Error"
+Done:
+FunctionEnd
+
+
;------------------------------------------------------------------------------
; Installer sections
;------------------------------------------------------------------------------
Section "IDEA Files" CopyIdeaFiles
-; StrCpy $baseRegKey "HKCU"
-; !insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 3" "State"
-; StrCmp $R2 1 continue_for_current_user
-; SetShellVarContext all
-; StrCpy $baseRegKey "HKLM"
-; continue_for_current_user:
-
-; create shortcuts
-
+ ${LineSum} "$INSTDIR\python\python.txt" $R0
+ IfErrors cantOpenFile
+ StrCmp $R0 "2" getPythonInfo ;info about 2 and 3 version of python
+cantOpenFile:
+ MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is invalid. Python will not be downloaded."
+ goto skip_python_download
+getPythonInfo:
+ Call getPythonInfo
+ StrCmp $0 "Error" skip_python_download
!insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 4" "State"
StrCmp $R2 1 "" python3
- StrCpy $R2 "2.7"
+ StrCpy $R2 $0
+ StrCpy $R3 $1
goto check_python
python3:
- StrCpy $R2 "3.4"
+ StrCpy $R2 $R0
+ StrCpy $R3 $R1
check_python:
- ReadRegStr $1 "HKCU" "Software\Python\PythonCore\$R2\InstallPath" $0
+ ReadRegStr $1 "HKCU" "Software\Python\PythonCore\$R2\InstallPath" ""
StrCmp $1 "" installation_for_all_users
goto verefy_python_launcher
installation_for_all_users:
- ReadRegStr $1 "HKLM" "Software\Python\PythonCore\$R2\InstallPath" $0
+ ReadRegStr $1 "HKLM" "Software\Python\PythonCore\$R2\InstallPath" ""
StrCmp $1 "" get_python
verefy_python_launcher:
IfFileExists $1\python.exe python_exists get_python
-
-get_python:
- CreateDirectory "$INSTDIR\python"
- StrCmp $R2 "2.7" get_python2
- inetc::get "https://www.python.org/ftp/python/3.4.1/python-3.4.1.amd64.msi" "$INSTDIR\python\python_$R2.msi"
+get_python:
+ inetc::get "$R3" "$INSTDIR\python\python_$R2.msi"
goto validate_download
-get_python2:
- inetc::get "http://www.python.org/ftp/python/2.7.8/python-2.7.8.msi" "$INSTDIR\python\python_$R2.msi"
-validate_download:
+validate_download:
Pop $0
${If} $0 == "OK"
- ExecCmd::exec 'msiexec /i "$INSTDIR\python\python_$R2.msi" /quiet /qn /norestart /log "$INSTDIR\python\python_$R2_silent.log"'
+ ExecCmd::exec 'msiexec /i "$INSTDIR\python\python_$R2.msi" /quiet /qn /norestart'
+ ${Else}
+ MessageBox MB_OK|MB_ICONEXCLAMATION "The download is failed"
${EndIf}
-
python_exists:
+skip_python_download:
+; create shortcuts
!insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 1" "State"
StrCmp $R2 1 "" skip_desktop_shortcut
CreateShortCut "$DESKTOP\${PRODUCT_FULL_NAME_WITH_VER}.lnk" \
@@ -882,6 +904,21 @@ Function ConfirmDesktopShortcut
${If} $0 == "1"
!insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 2" "Flags" "DISABLED"
${EndIf}
+ CreateDirectory "$INSTDIR\python"
+ inetc::get "http://www.jetbrains.com/updates/python.txt" "$INSTDIR\python\python.txt"
+ ${LineSum} "$INSTDIR\python\python.txt" $R0
+ IfErrors cantOpenFile
+ StrCmp $R0 "2" getPythonInfo
+cantOpenFile:
+ MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is not exist. Python will not be downloaded."
+ goto association
+getPythonInfo:
+ Call getPythonInfo
+ StrCmp $0 "Error" association
+ !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 4" "Text" "Python $0"
+ !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 5" "Text" "Python $R0"
+
+association:
StrCmp "${ASSOCIATION}" "NoAssociation" skip_association
StrCpy $R0 6
push "${ASSOCIATION}"
diff --git a/python/edu/build/paths.nsi b/python/edu/build/paths.nsi
index 910f2b3555b7..d85492988c41 100644
--- a/python/edu/build/paths.nsi
+++ b/python/edu/build/paths.nsi
@@ -1,5 +1,5 @@
; Installer images
-!define IMAGES_LOCATION ${COMMUNITY_DIR}\python\build\resources
+!define IMAGES_LOCATION ${COMMUNITY_DIR}\python\edu\build\resources
;!define LICENSE_FILE ${BASE_DIR}\python\license\PyCharm_Preview_License
!define PRODUCT_PROPERTIES_FILE ${BASE_DIR}\out\pycharmEDU\layout\bin\idea.properties
!define PRODUCT_VM_OPTIONS_NAME pycharm.exe.vmoptions
diff --git a/python/edu/build/plugin-list.txt b/python/edu/build/plugin-list.txt
index bc4b94c9e7f4..60ecf3ff6f2d 100644
--- a/python/edu/build/plugin-list.txt
+++ b/python/edu/build/plugin-list.txt
@@ -4,4 +4,5 @@ remote-servers-git
github
IntelliLang
IntelliLang-python
-learn-python \ No newline at end of file
+learn-python
+course-creator \ No newline at end of file
diff --git a/python/edu/build/pycharm_edu_build.gant b/python/edu/build/pycharm_edu_build.gant
index 2ef0bed607a7..28d829e68b34 100644
--- a/python/edu/build/pycharm_edu_build.gant
+++ b/python/edu/build/pycharm_edu_build.gant
@@ -22,14 +22,17 @@ includeTargets << new File("$home/community/build/scripts/libLicenses.gant")
includeTargets << new File("$home/build/scripts/ultimate_utils.gant")
requireProperty("buildNumber", requireProperty("build.number", snapshot))
-setProperty("buildName", "PE-$buildNumber")
+setProperty("buildName", "EDU-$buildNumber")
setProperty("ch", "$home/community")
setProperty("pythonCommunityHome", "$ch/python")
setProperty("pythonEduHome", "$ch/python/edu")
+requireProperty("jdk_bundled_mac", "1.7")
+def jdk_bundled_version = p("jdk_bundled_mac") == "1.8" ? "jdk8_mac_redist.tar" : "jdk_mac_redist.tar"
+ant.copy(file: "${home}/build/jdk/${jdk_bundled_version}", tofile: "${home}/build/jdk/jdk_mac_redist_for_${buildNumber}.tar")
// load ApplicationInfo.xml properties
ant.xmlproperty(file: "$pythonEduHome/resources/idea/PyCharmEduApplicationInfo.xml", collapseAttributes: "true")
-setProperty("system_selector", "PyCharm${p("component.version.major")}0")
+setProperty("system_selector", "PyCharmEdu${p("component.version.major")}0")
setProperty("dryRun", false)
setProperty("jdk16", guessJdk())
@@ -89,7 +92,6 @@ class Paths {
}
setProperty("paths", new Paths(home))
-setProperty("buildName", "PE-$buildNumber")
target('default': "Build artifacts") {
@@ -196,12 +198,12 @@ public layoutEducational(String classesPath, Set usedJars) {
buildNSIS([paths.distAll, paths.distWin],
"$pythonEduHome/build/strings.nsi", "$pythonEduHome/build/paths.nsi",
- "pycharmPE-", false, true, ".py", system_selector)
+ "pycharmEDU-", false, true, ".py", system_selector)
- String tarRoot = isEap() ? "pycharm-pe-$buildNumber" : "pycharm-pe-${p("component.version.major")}.${p("component.version.minor")}"
+ String tarRoot = isEap() ? "pycharm-edu-$buildNumber" : "pycharm-edu-${p("component.version.major")}.${p("component.version.minor")}"
buildTarGz(tarRoot, "$paths.artifacts/pycharm${buildName}.tar", [paths.distAll, paths.distUnix])
- String macAppRoot = isEap() ? "PyCharm PE ${p("component.version.major")}.${p("component.version.minor")} EAP.app/Contents" : "PyCharm PE.app/Contents"
+ String macAppRoot = isEap() ? "PyCharm Educational ${p("component.version.major")}.${p("component.version.minor")} EAP.app/Contents" : "PyCharm Educational.app/Contents"
buildMacZip(macAppRoot, "${paths.artifacts}/pycharm${buildName}.sit", [paths.distAll], paths.distMac)
ant.copy(file: "${paths.artifacts}/pycharm${buildName}.sit", tofile: "${paths.artifacts}/pycharm${buildName}-jdk-bundled.sit")
ant.delete(file: "${paths.artifacts}/pycharm${buildName}.sit")
@@ -214,6 +216,7 @@ private layoutPlugins(layouts) {
fileset(dir: "$pythonEduHome/learn-python/resources/courses")
}
}
+ layouts.layoutPlugin("course-creator")
}
layouts.layoutCommunityPlugins(ch)
@@ -363,7 +366,7 @@ private layoutWin(Map args, String target) {
winScripts(target, ch, "pycharm.bat", args)
winVMOptions(target, null, "pycharm.exe")
- ant.copy(file: "$home/python/help/pycharmhelp.jar", todir: "$target/help", failonerror: false)
+ ant.copy(file: "$home/python/help/pycharm-eduhelp.jar", todir: "$target/help", failonerror: false)
}
private layoutUnix(Map args, String target) {
@@ -380,7 +383,7 @@ private layoutUnix(Map args, String target) {
unixScripts(target, ch, "pycharm.sh", args)
unixVMOptions(target, "pycharm")
- ant.copy(file: "$home/python/help/pycharmhelp.jar", todir: "$target/help", failonerror: false)
+ ant.copy(file: "$home/python/help/pycharm-eduhelp.jar", todir: "$target/help", failonerror: false)
}
private layoutMac(Map _args, String target) {
diff --git a/python/edu/build/python.txt b/python/edu/build/python.txt
new file mode 100644
index 000000000000..d6f247d29afb
--- /dev/null
+++ b/python/edu/build/python.txt
@@ -0,0 +1,2 @@
+python2 2.7 https://www.python.org/ftp/python/2.7.8/python-2.7.8.amd64.msi
+python3 3.4 https://www.python.org/ftp/python/3.4.1/python-3.4.1.amd64.msi \ No newline at end of file
diff --git a/python/edu/build/resources/logo.bmp b/python/edu/build/resources/logo.bmp
new file mode 100644
index 000000000000..9ff6698b8e36
--- /dev/null
+++ b/python/edu/build/resources/logo.bmp
Binary files differ
diff --git a/python/edu/build/resources/logo.png b/python/edu/build/resources/logo.png
deleted file mode 100644
index 1ccbc0773837..000000000000
--- a/python/edu/build/resources/logo.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/build/upload_pythonInfo.xml b/python/edu/build/upload_pythonInfo.xml
new file mode 100644
index 000000000000..f8d9477d1a3f
--- /dev/null
+++ b/python/edu/build/upload_pythonInfo.xml
@@ -0,0 +1,32 @@
+<project name="Upload updates.xml to jetbrains.com. Effective in half an hour" default="bootstrap">
+ <property name="home" value="${basedir}/../../../.."/>
+ <target name="upload">
+ <xmlvalidate file="${home}/build/eap/updates.xml"/>
+
+ <property name="host" value="ftp.labs.intellij.net"/>
+ <property name="user" value="idea"/>
+ <property name="password" value="4pawoMauoJjjlxpIl3XG"/>
+
+ <ftp server="${host}" action="send" binary="false" remotedir="updates" userid="${user}" password="${password}">
+ <fileset file="${home}/community/python/edu/build/python.txt"/>
+ </ftp>
+ </target>
+
+ <target name="bootstrap">
+ <java failonerror="true" classname="org.apache.tools.ant.Main" fork="true">
+ <classpath>
+ <fileset dir="${home}/community/lib/ant/lib">
+ <include name="*.jar"/>
+ </fileset>
+ <fileset dir="${home}/community/lib">
+ <include name="commons-net-3.1.jar"/>
+ <include name="jsch-0.1.50.jar"/>
+ </fileset>
+ </classpath>
+
+ <arg value="-f"/>
+ <arg value="${ant.file}"/>
+ <arg value="upload"/>
+ </java>
+ </target>
+</project>
diff --git a/python/edu/course-creator/course-creator.iml b/python/edu/course-creator/course-creator.iml
new file mode 100644
index 000000000000..fc8c6a1462b1
--- /dev/null
+++ b/python/edu/course-creator/course-creator.iml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="python-community" />
+ <orderEntry type="module" module-name="lang-impl" />
+ <orderEntry type="library" name="gson" level="project" />
+ </component>
+</module>
+
diff --git a/python/edu/course-creator/resources/META-INF/plugin.xml b/python/edu/course-creator/resources/META-INF/plugin.xml
new file mode 100644
index 000000000000..6a9a9ea90276
--- /dev/null
+++ b/python/edu/course-creator/resources/META-INF/plugin.xml
@@ -0,0 +1,60 @@
+<idea-plugin version="2">
+ <id>org.jetbrains.plugins.coursecreator</id>
+ <name>Course Creator for PyCharm Educational</name>
+ <version>1.0</version>
+
+ <description><![CDATA[
+ Plugin allows you to create new course for PyCharm Education Edition.
+ ]]></description>
+
+ <change-notes><![CDATA[
+ ]]>
+ </change-notes>
+ <!-- please see http://confluence.jetbrains.com/display/IDEADEV/Build+Number+Ranges for description -->
+
+ <!-- please see http://confluence.jetbrains.com/display/IDEADEV/Plugin+Compatibility+with+IntelliJ+Platform+Products
+ on how to target different products -->
+ <!-- uncomment to enable plugin in all products
+ <depends>com.intellij.modules.lang</depends>
+ -->
+
+ <depends>com.intellij.modules.python</depends>
+
+ <extensions defaultExtensionNs="com.intellij">
+ <directoryProjectGenerator implementation="org.jetbrains.plugins.coursecreator.CCProjectGenerator"/>
+ <projectService serviceImplementation="org.jetbrains.plugins.coursecreator.CCProjectService"/>
+ <codeInsight.lineMarkerProvider language="Python"
+ implementationClass="org.jetbrains.plugins.coursecreator.highlighting.CCTaskLineMarkerProvider"/>
+ <treeStructureProvider implementation="org.jetbrains.plugins.coursecreator.projectView.CCTreeStructureProvider"/>
+ </extensions>
+
+ <application-components>
+ <!-- Add your application components here -->
+ </application-components>
+
+ <project-components>
+ <!-- Add your project components here -->
+ <component>
+ <implementation-class>org.jetbrains.plugins.coursecreator.CCProjectComponent</implementation-class>
+ </component>
+ </project-components>
+
+ <actions>
+ <action id="CreateLesson" class="org.jetbrains.plugins.coursecreator.actions.CreateLesson">
+ <add-to-group group-id="NewGroup" anchor="before" relative-to-action="NewFile"/>
+ </action>
+ <action id="CreateTaskFile" class="org.jetbrains.plugins.coursecreator.actions.CreateTaskFile">
+ <add-to-group group-id="NewGroup" anchor="before" relative-to-action="NewFile"/>
+ </action>
+ <action id="CreateTask" class="org.jetbrains.plugins.coursecreator.actions.CreateTask">
+ <add-to-group group-id="NewGroup" anchor="before" relative-to-action="NewFile"/>
+ </action>
+ <action id="AddTaskWindow" class="org.jetbrains.plugins.coursecreator.actions.AddTaskWindow">
+ <add-to-group group-id="EditorPopupMenu" anchor="before" relative-to-action="CopyReference"/>
+ </action>
+ <action id="PackCourse" class="org.jetbrains.plugins.coursecreator.actions.CreateCourseArchive">
+ <add-to-group group-id="MainToolBar" anchor="last" />
+ </action>
+ </actions>
+
+</idea-plugin> \ No newline at end of file
diff --git a/python/edu/course-creator/resources/fileTemplates/internal/task.html.ft b/python/edu/course-creator/resources/fileTemplates/internal/task.html.ft
new file mode 100644
index 000000000000..f6833496d1c1
--- /dev/null
+++ b/python/edu/course-creator/resources/fileTemplates/internal/task.html.ft
@@ -0,0 +1,4 @@
+<html>
+Write your task text here.
+<br>
+</html> \ No newline at end of file
diff --git a/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft b/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft
new file mode 100644
index 000000000000..0256e108786c
--- /dev/null
+++ b/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft
@@ -0,0 +1 @@
+# TODO: type solution here \ No newline at end of file
diff --git a/python/edu/course-creator/resources/fileTemplates/internal/test_helper.py.ft b/python/edu/course-creator/resources/fileTemplates/internal/test_helper.py.ft
new file mode 100644
index 000000000000..1182b7815c06
--- /dev/null
+++ b/python/edu/course-creator/resources/fileTemplates/internal/test_helper.py.ft
@@ -0,0 +1,127 @@
+import sys
+
+def get_file_text(path):
+ """ get file text by path"""
+ file_io = open(path, "r")
+ text = file_io.read()
+ file_io.close()
+ return text
+
+def get_file_output(path):
+ # TODO: get file output by path
+ return ""
+
+def test_file_importable():
+ """ tests there is no obvious syntax errors"""
+ path = sys.argv[-1]
+ try:
+ import_file(path)
+ except ImportError:
+ failed("File contains syntax errors")
+ return
+ except SyntaxError:
+ failed("File contains syntax errors")
+ return
+ except NameError:
+ failed("File contains syntax errors")
+ return
+
+ passed()
+
+def import_file(path):
+ """ returns imported file """
+ import imp
+ tmp = imp.load_source('tmp', path)
+ return tmp
+
+def import_task_file():
+ """ returns imported file """
+ path = sys.argv[-1]
+ return import_file(path)
+
+def test_is_not_empty():
+ path = sys.argv[-1]
+ file_text = get_file_text(path)
+
+ if len(file_text) > 0:
+ passed()
+ else:
+ failed("The file is empty. Please, reload the task and try again.")
+
+def test_is_initial_text(error_text="You should modify the file"):
+ path = sys.argv[-1]
+ text = get_initial_text(path)
+ file_text = get_file_text(path)
+
+ if file_text.strip() == text:
+ failed(error_text)
+ else:
+ passed()
+
+def get_initial_text(path):
+ course_lib = sys.argv[-2]
+
+ import os
+ # path format is "project_root/lessonX/taskY/file.py"
+ task_index = path.rfind(os.sep, 0, path.rfind(os.sep))
+ index = path.rfind(os.sep, 0, task_index)
+ relative_path = path[index+1:]
+ initial_file_path = os.path.join(course_lib, relative_path)
+ return get_file_text(initial_file_path)
+
+
+def test_text_equals(text, error_text):
+ path = sys.argv[-1]
+ file_text = get_file_text(path)
+
+ if file_text.strip() == text:
+ passed()
+ else:
+ failed(error_text)
+
+def test_window_text_deleted(error_text="Don't just delete task text"):
+ windows = get_task_windows()
+
+ for window in windows:
+ if len(window) == 0:
+ failed(error_text)
+ return
+ passed()
+
+
+def failed(message="Please, reload the task and try again."):
+ print("#study_plugin FAILED + " + message)
+
+def passed():
+ print("#study_plugin test OK")
+
+def get_task_windows():
+ prefix = "#study_plugin_window = "
+ path = sys.argv[-1]
+ import os
+ windows_path = os.path.splitext(path)[0] + "_windows"
+ windows = []
+ f = open(windows_path, "r")
+ window_text = ""
+ first = True
+ for line in f.readlines():
+ if line.startswith(prefix):
+ if not first:
+ windows.append(window_text.strip())
+ else:
+ first = False
+ window_text = line[len(prefix):]
+ else:
+ window_text += line
+
+ if window_text:
+ windows.append(window_text.strip())
+
+ f.close()
+ return windows
+
+def run_common_tests(error_text="Please, reload file and try again"):
+ test_file_importable()
+ test_is_not_empty()
+ test_is_initial_text(error_text)
+ test_window_text_deleted(error_text) \ No newline at end of file
diff --git a/python/edu/course-creator/resources/fileTemplates/internal/tests.py.ft b/python/edu/course-creator/resources/fileTemplates/internal/tests.py.ft
new file mode 100644
index 000000000000..2e6fd4c78eed
--- /dev/null
+++ b/python/edu/course-creator/resources/fileTemplates/internal/tests.py.ft
@@ -0,0 +1,17 @@
+from test_helper import run_common_tests, failed, passed, get_task_windows
+
+
+def test_task_windows():
+ windows = get_task_windows()
+ window = windows[0]
+ if window != "": # TODO: your condition here
+ passed()
+ else:
+ failed()
+
+
+if __name__ == '__main__':
+ run_common_tests()
+ test_task_windows()
+
+
diff --git a/python/edu/course-creator/resources/icons/gutter.png b/python/edu/course-creator/resources/icons/gutter.png
new file mode 100644
index 000000000000..244e6ca045c5
--- /dev/null
+++ b/python/edu/course-creator/resources/icons/gutter.png
Binary files differ
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;
+ }
+}
diff --git a/python/edu/learn-python/gen/icons/StudyIcons.java b/python/edu/learn-python/gen/icons/StudyIcons.java
index 28409103ea00..04ae98da1672 100644
--- a/python/edu/learn-python/gen/icons/StudyIcons.java
+++ b/python/edu/learn-python/gen/icons/StudyIcons.java
@@ -13,17 +13,15 @@ public class StudyIcons {
return IconLoader.getIcon(path, StudyIcons.class);
}
- public static final Icon Add = load("/icons/com/jetbrains/python/edu/add.png"); // 16x16
- public static final Icon Checked = load("/icons/com/jetbrains/python/edu/checked.png"); // 32x32
- public static final Icon Failed = load("/icons/com/jetbrains/python/edu/failed.png"); // 32x32
- public static final Icon Next = load("/icons/com/jetbrains/python/edu/next.png"); // 24x24
- public static final Icon Playground = load("/icons/com/jetbrains/python/edu/playground.png"); // 32x28
- public static final Icon Prev = load("/icons/com/jetbrains/python/edu/prev.png"); // 24x24
- public static final Icon Refresh = load("/icons/com/jetbrains/python/edu/refresh.png"); // 16x16
- public static final Icon Refresh24 = load("/icons/com/jetbrains/python/edu/refresh24.png"); // 24x24
- public static final Icon Resolve = load("/icons/com/jetbrains/python/edu/resolve.png"); // 24x24
- public static final Icon Run = load("/icons/com/jetbrains/python/edu/Run.png"); // 24x24
+ public static final Icon EducationalProjectType = load("/icons/com/jetbrains/python/edu/EducationalProjectType.png"); // 32x32
+ public static final Icon Lesson = load("/icons/com/jetbrains/python/edu/Lesson.png"); // 16x16
+ public static final Icon LessonCompl = load("/icons/com/jetbrains/python/edu/LessonCompl.png"); // 16x16
+ public static final Icon Playground = load("/icons/com/jetbrains/python/edu/Playground.png"); // 16x16
+ public static final Icon Prev = load("/icons/com/jetbrains/python/edu/prev.png"); // 16x16
+ public static final Icon Resolve = load("/icons/com/jetbrains/python/edu/resolve.png"); // 16x16
public static final Icon ShowHint = load("/icons/com/jetbrains/python/edu/showHint.png"); // 24x24
- public static final Icon Unchecked = load("/icons/com/jetbrains/python/edu/unchecked.png"); // 32x32
+ public static final Icon Task = load("/icons/com/jetbrains/python/edu/Task.png"); // 16x16
+ public static final Icon TaskCompl = load("/icons/com/jetbrains/python/edu/TaskCompl.png"); // 16x16
+ public static final Icon TaskProbl = load("/icons/com/jetbrains/python/edu/TaskProbl.png"); // 16x16
public static final Icon WatchInput = load("/icons/com/jetbrains/python/edu/WatchInput.png"); // 24x24
}
diff --git a/python/edu/learn-python/learn-python.iml b/python/edu/learn-python/learn-python.iml
index bd539d1e2147..613d67594a7a 100644
--- a/python/edu/learn-python/learn-python.iml
+++ b/python/edu/learn-python/learn-python.iml
@@ -15,6 +15,7 @@
<orderEntry type="module" module-name="lang-impl" />
<orderEntry type="library" name="gson" level="project" />
<orderEntry type="library" name="JUnit4" level="project" />
+ <orderEntry type="module" module-name="python-ide-community" />
</component>
</module>
diff --git a/python/edu/learn-python/resources/META-INF/plugin.xml b/python/edu/learn-python/resources/META-INF/plugin.xml
index ec828eb44d59..8e8bddcce5e5 100644
--- a/python/edu/learn-python/resources/META-INF/plugin.xml
+++ b/python/edu/learn-python/resources/META-INF/plugin.xml
@@ -50,7 +50,7 @@
<action id="NextTaskAction" class="com.jetbrains.python.edu.actions.StudyNextStudyTaskAction" text="NextTaskAction" description="Next Task"/>
<action id="PreviousTaskAction" class="com.jetbrains.python.edu.actions.StudyPreviousStudyTaskAction" text="PreviousTaskAction"
description="Previous Task"/>
- <action id="RefreshTaskAction" class="com.jetbrains.python.edu.actions.StudyRefreshTaskAction" text="RefreshTaskAction"
+ <action id="RefreshTaskAction" class="com.jetbrains.python.edu.actions.StudyRefreshTaskFileAction" text="RefreshTaskAction"
description="Refresh current task"/>
<action id="WatchInputAction" class="com.jetbrains.python.edu.actions.StudyEditInputAction" text="WatchInputAction"
description="watch input"/>
@@ -59,6 +59,11 @@
description="show hint">
<add-to-group group-id="MainToolBar" anchor="last"/>
</action>
+
+ <action id="WelcomeScreen.LearnPython" class="com.jetbrains.python.edu.actions.StudyNewProject" icon="StudyIcons.EducationalProjectType">
+ <add-to-group group-id="WelcomeScreen.QuickStart" anchor="first"/>
+ </action>
+
</actions>
<extensions defaultExtensionNs="com.intellij">
@@ -70,4 +75,7 @@
<applicationService serviceInterface="com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter"
serviceImplementation="com.jetbrains.python.edu.StudyInstructionPainter" overrides="true"/>
</extensions>
+ <extensions defaultExtensionNs="Pythonid">
+ <visitorFilter language="Python" implementationClass="com.jetbrains.python.edu.highlighting.StudyVisitorFilter"/>
+ </extensions>
</idea-plugin> \ No newline at end of file
diff --git a/python/edu/learn-python/resources/courses/introduction_course.zip b/python/edu/learn-python/resources/courses/introduction_course.zip
index f3b24f24b1e2..c39695e0c380 100644
--- a/python/edu/learn-python/resources/courses/introduction_course.zip
+++ b/python/edu/learn-python/resources/courses/introduction_course.zip
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType.png
new file mode 100644
index 000000000000..6340e89b1f3f
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType_dark.png
new file mode 100644
index 000000000000..d9ec6dc45b0c
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson.png
new file mode 100644
index 000000000000..9cc8a4f84f06
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson@2x.png
new file mode 100644
index 000000000000..2b0c0b83271b
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson@2x.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonComp@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonComp@2x.png
new file mode 100644
index 000000000000..e18c6391d0ac
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonComp@2x.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonCompl.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonCompl.png
new file mode 100644
index 000000000000..bacc7ded6755
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonCompl.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground.png
new file mode 100644
index 000000000000..10867103374a
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground@2x.png
new file mode 100644
index 000000000000..58665fa41a1b
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground@2x.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Run.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Run.png
deleted file mode 100644
index 27a6e362cded..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Run.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task.png
new file mode 100644
index 000000000000..b678c649b303
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x.png
new file mode 100644
index 000000000000..4abb95ce7c9e
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x_dark.png
new file mode 100644
index 000000000000..78af6914c933
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl.png
new file mode 100644
index 000000000000..a7f8f7792823
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x.png
new file mode 100644
index 000000000000..d657aa68f7df
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x_dark.png
new file mode 100644
index 000000000000..f5f29ee3e68e
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl_dark.png
new file mode 100644
index 000000000000..481e9cd2fbbd
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl.png
new file mode 100644
index 000000000000..6173d6423385
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x.png
new file mode 100644
index 000000000000..44aba9d1c6fb
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x_dark.png
new file mode 100644
index 000000000000..f74c9de5398c
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl_dark.png
new file mode 100644
index 000000000000..0133a7f75505
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task_dark.png
new file mode 100644
index 000000000000..2ff286dd28f6
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/add.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/add.png
deleted file mode 100644
index 9494f2d0c72e..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/add.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/checked.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/checked.png
deleted file mode 100644
index 4105a01f1353..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/checked.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/failed.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/failed.png
deleted file mode 100644
index e2aaa556056e..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/failed.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/icon.jpg b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/icon.jpg
deleted file mode 100644
index 3a9716e4e1fb..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/icon.jpg
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/next.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/next.png
deleted file mode 100644
index dd1a5d9aebf3..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/next.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/playground.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/playground.png
deleted file mode 100644
index d12a751c0c40..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/playground.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png
index 0656f81eee83..fc51cb5861b1 100644
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh.png
deleted file mode 100644
index d595f6b42f56..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh24.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh24.png
deleted file mode 100644
index 218f075d0c18..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh24.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png
index 7ef960bcf244..78290f936f68 100644
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png
index 99aaa1d20a32..b988adc56843 100644
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/unchecked.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/unchecked.png
deleted file mode 100644
index 2145982cf2be..000000000000
--- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/unchecked.png
+++ /dev/null
Binary files differ
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java
index d4831d93e367..59bd8bc2ea7c 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java
@@ -53,7 +53,7 @@ public class StudyDirectoryProjectGenerator extends PythonProjectGenerator imple
@NotNull
@Override
public String getName() {
- return "Study project";
+ return "Learn Python";
}
@@ -137,7 +137,7 @@ public class StudyDirectoryProjectGenerator extends PythonProjectGenerator imple
@Nullable
@Override
public Icon getLogo() {
- return StudyIcons.Playground;
+ return StudyIcons.EducationalProjectType;
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java
index 9fdcf704a29b..6ce1d0991dff 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java
@@ -52,6 +52,9 @@ public class StudyDocumentListener extends DocumentAdapter {
if (myTaskWindow != null) {
int newLength = myTaskWindow.getLength() + change;
myTaskWindow.setLength(newLength <= 0 ? 0 : newLength);
+ if (e.getNewFragment().equals("\n")) {
+ myTaskWindow.setLength(myTaskWindow.getLength() + 1);
+ }
}
int newEnd = offset + event.getNewLength();
int newLine = document.getLineNumber(newEnd);
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java
index 4f34bfb92fab..96a44b2ee66a 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java
@@ -8,7 +8,6 @@ import com.intellij.ui.JBColor;
import com.intellij.util.PairFunction;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.UIUtil;
-import com.jetbrains.python.edu.ui.StudyCondition;
import java.awt.*;
@@ -19,10 +18,6 @@ import java.awt.*;
public class StudyInstructionPainter extends EditorEmptyTextPainter {
@Override
public void paintEmptyText(final EditorsSplitters splitters, Graphics g) {
- if (!StudyCondition.VALUE) {
- super.paintEmptyText(splitters, g);
- return;
- }
boolean isDarkBackground = UIUtil.isUnderDarcula();
UIUtil.applyRenderingHints(g);
GraphicsUtil.setupAntialiasing(g, true, false);
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyState.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyState.java
new file mode 100644
index 000000000000..96dc3d906472
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyState.java
@@ -0,0 +1,52 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.editor.StudyEditor;
+
+public class StudyState {
+ private final StudyEditor myStudyEditor;
+ private final Editor myEditor;
+ private final TaskFile myTaskFile;
+ private final VirtualFile myVirtualFile;
+ private final Task myTask;
+ private final VirtualFile myTaskDir;
+
+ public StudyState(final StudyEditor studyEditor) {
+ myStudyEditor = studyEditor;
+ myEditor = studyEditor != null ? studyEditor.getEditor() : null;
+ myTaskFile = studyEditor != null ? studyEditor.getTaskFile() : null;
+ myVirtualFile = myEditor != null ? FileDocumentManager.getInstance().getFile(myEditor.getDocument()) : null;
+ myTaskDir = myVirtualFile != null ? myVirtualFile.getParent() : null;
+ myTask = myTaskFile != null ? myTaskFile.getTask() : null;
+ }
+
+ public Editor getEditor() {
+ return myEditor;
+ }
+
+ public TaskFile getTaskFile() {
+ return myTaskFile;
+ }
+
+ public VirtualFile getVirtualFile() {
+ return myVirtualFile;
+ }
+
+ public Task getTask() {
+ return myTask;
+ }
+
+ public VirtualFile getTaskDir() {
+ return myTaskDir;
+ }
+
+ public boolean isValid() {
+ return myStudyEditor != null && myEditor != null &&
+ myTaskFile != null && myVirtualFile != null &&
+ myTask != null && myTaskDir != null;
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java
index 213c1f7601f0..3013fbcb05d3 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java
@@ -109,21 +109,29 @@ public class StudyTaskManager implements ProjectComponent, PersistentStateCompon
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() {
@Override
public void run() {
- ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW).show(null);
- FileEditor[] editors = FileEditorManager.getInstance(myProject).getSelectedEditors();
- if (editors.length > 0) {
- JComponent focusedComponent = editors[0].getPreferredFocusedComponent();
- if (focusedComponent != null) {
- IdeFocusManager.getInstance(myProject).requestFocus(focusedComponent, true);
+ ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW).show(new Runnable() {
+ @Override
+ public void run() {
+ FileEditor[] editors = FileEditorManager.getInstance(myProject).getSelectedEditors();
+ if (editors.length > 0) {
+ final JComponent focusedComponent = editors[0].getPreferredFocusedComponent();
+ if (focusedComponent != null) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ IdeFocusManager.getInstance(myProject).requestFocus(focusedComponent, true);
+ }
+ });
+ }
+ }
}
- }
+ });
}
});
UISettings.getInstance().HIDE_TOOL_STRIPES = false;
UISettings.getInstance().fireUISettingsChanged();
ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
String toolWindowId = StudyToolWindowFactory.STUDY_TOOL_WINDOW;
- //TODO:decide smth with tool window position
try {
Method method = toolWindowManager.getClass().getDeclaredMethod("registerToolWindow", String.class,
JComponent.class,
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java
new file mode 100644
index 000000000000..b0cd5ba89fed
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java
@@ -0,0 +1,76 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.edu.course.Course;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.sdk.PythonSdkType;
+
+import java.io.*;
+import java.util.Map;
+
+public class StudyTestRunner {
+ public static final String TEST_OK = "#study_plugin test OK";
+ private static final String TEST_FAILED = "#study_plugin FAILED + ";
+ private static final String PYTHONPATH = "PYTHONPATH";
+ private static final Logger LOG = Logger.getInstance(StudyTestRunner.class);
+ private final Task myTask;
+ private final VirtualFile myTaskDir;
+
+ public StudyTestRunner(Task task, VirtualFile taskDir) {
+ myTask = task;
+ myTaskDir = taskDir;
+ }
+
+ public Process launchTests(Project project, String executablePath) throws ExecutionException {
+ Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
+ File testRunner = new File(myTaskDir.getPath(), myTask.getTestFile());
+ GeneralCommandLine commandLine = new GeneralCommandLine();
+ commandLine.setWorkDirectory(myTaskDir.getPath());
+ final Map<String, String> env = commandLine.getEnvironment();
+ final VirtualFile courseDir = project.getBaseDir();
+ if (courseDir != null) {
+ env.put(PYTHONPATH, courseDir.getPath());
+ }
+ if (sdk != null) {
+ String pythonPath = sdk.getHomePath();
+ if (pythonPath != null) {
+ commandLine.setExePath(pythonPath);
+ commandLine.addParameter(testRunner.getPath());
+ final Course course = StudyTaskManager.getInstance(project).getCourse();
+ assert course != null;
+ commandLine.addParameter(new File(course.getResourcePath()).getParent());
+ commandLine.addParameter(FileUtil.toSystemDependentName(executablePath));
+ return commandLine.createProcess();
+ }
+ }
+ return null;
+ }
+
+
+ public String getPassedTests(Process p) {
+ InputStream testOutput = p.getInputStream();
+ BufferedReader testOutputReader = new BufferedReader(new InputStreamReader(testOutput));
+ String line;
+ try {
+ while ((line = testOutputReader.readLine()) != null) {
+ if (line.contains(TEST_FAILED)) {
+ return line.substring(TEST_FAILED.length(), line.length());
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ finally {
+ StudyUtils.closeSilently(testOutputReader);
+ }
+ return TEST_OK;
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java
index d3ac1dadf98e..5d9bb139db09 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java
@@ -3,6 +3,7 @@ package com.jetbrains.python.edu;
import com.intellij.ide.SaveAndSyncHandlerImpl;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
@@ -10,12 +11,12 @@ import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.util.ui.UIUtil;
-import com.jetbrains.python.edu.course.TaskFile;
-import com.jetbrains.python.edu.course.TaskWindow;
+import com.jetbrains.python.edu.course.*;
import com.jetbrains.python.edu.editor.StudyEditor;
import com.jetbrains.python.edu.ui.StudyToolWindowFactory;
import org.jetbrains.annotations.NotNull;
@@ -70,7 +71,7 @@ public class StudyUtils {
return wrapHTML ? UIUtil.toHtml(taskText.toString()) : taskText.toString();
}
catch (IOException e) {
- LOG.error("Failed to get file text from file " + fileName, e);
+ LOG.info("Failed to get file text from file " + fileName, e);
}
finally {
closeSilently(reader);
@@ -119,14 +120,18 @@ public class StudyUtils {
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
- public static VirtualFile flushWindows(Document document, TaskFile taskFile, VirtualFile file) {
+ public static VirtualFile flushWindows(TaskFile taskFile, VirtualFile file) {
VirtualFile taskDir = file.getParent();
VirtualFile fileWindows = null;
+ final Document document = FileDocumentManager.getInstance().getDocument(file);
+ if (document == null) {
+ LOG.debug("Couldn't flush windows");
+ return null;
+ }
if (taskDir != null) {
String name = file.getNameWithoutExtension() + "_windows";
PrintWriter printWriter = null;
try {
-
fileWindows = taskDir.createChildData(taskFile, name);
printWriter = new PrintWriter(new FileOutputStream(fileWindows.getPath()));
for (TaskWindow taskWindow : taskFile.getTaskWindows()) {
@@ -137,6 +142,12 @@ public class StudyUtils {
String windowDescription = document.getText(new TextRange(start, start + taskWindow.getLength()));
printWriter.println("#study_plugin_window = " + windowDescription);
}
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ FileDocumentManager.getInstance().saveDocument(document);
+ }
+ });
}
catch (IOException e) {
LOG.error(e);
@@ -148,4 +159,27 @@ public class StudyUtils {
}
return fileWindows;
}
+
+ public static void deleteFile(VirtualFile file) {
+ try {
+ file.delete(StudyUtils.class);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+
+ public static File copyResourceFile(String sourceName, String copyName, Project project, Task task)
+ throws IOException {
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+ Course course = taskManager.getCourse();
+ int taskNum = task.getIndex() + 1;
+ int lessonNum = task.getLesson().getIndex() + 1;
+ assert course != null;
+ String pathToResource =
+ FileUtil.join(new File(course.getResourcePath()).getParent(), Lesson.LESSON_DIR + lessonNum, Task.TASK_DIR + taskNum);
+ File resourceFile = new File(pathToResource, copyName);
+ FileUtil.copy(new File(pathToResource, sourceName), resourceFile);
+ return resourceFile;
+ }
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
index f8e10c9c4521..5d02f7149aab 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
@@ -1,7 +1,6 @@
package com.jetbrains.python.edu.actions;
import com.intellij.execution.ExecutionException;
-import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnActionEvent;
@@ -13,93 +12,81 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
-import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.ui.JBColor;
+import com.intellij.openapi.wm.IdeFocusManager;
import com.jetbrains.python.edu.StudyDocumentListener;
-import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyState;
+import com.jetbrains.python.edu.StudyTestRunner;
import com.jetbrains.python.edu.StudyUtils;
-import com.jetbrains.python.edu.course.*;
+import com.jetbrains.python.edu.course.StudyStatus;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.TaskWindow;
import com.jetbrains.python.edu.editor.StudyEditor;
-import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
-import java.io.*;
-import java.util.*;
-import java.util.List;
+import java.io.IOException;
+import java.util.Map;
public class StudyCheckAction extends DumbAwareAction {
private static final Logger LOG = Logger.getInstance(StudyCheckAction.class.getName());
- public static final String PYTHONPATH = "PYTHONPATH";
+ private static final String ANSWERS_POSTFIX = "_answers.py";
- static class StudyTestRunner {
- public static final String TEST_OK = "#study_plugin test OK";
- private static final String TEST_FAILED = "#study_plugin FAILED + ";
- private final Task myTask;
- private final VirtualFile myTaskDir;
- StudyTestRunner(Task task, VirtualFile taskDir) {
- myTask = task;
- myTaskDir = taskDir;
+ private static void flushWindows(@NotNull final Task task, @NotNull final VirtualFile taskDir) {
+ for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ VirtualFile virtualFile = taskDir.findChild(name);
+ if (virtualFile == null) {
+ continue;
+ }
+ StudyUtils.flushWindows(taskFile, virtualFile);
}
+ }
- Process launchTests(Project project, String executablePath) throws ExecutionException {
- Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
- File testRunner = new File(myTaskDir.getPath(), myTask.getTestFile());
- GeneralCommandLine commandLine = new GeneralCommandLine();
- commandLine.setWorkDirectory(myTaskDir.getPath());
- final Map<String, String> env = commandLine.getEnvironment();
- final VirtualFile courseDir = project.getBaseDir();
- if (courseDir != null)
- env.put(PYTHONPATH, courseDir.getPath());
- if (sdk != null) {
- String pythonPath = sdk.getHomePath();
- if (pythonPath != null) {
- commandLine.setExePath(pythonPath);
- commandLine.addParameter(testRunner.getPath());
- final Course course = StudyTaskManager.getInstance(project).getCourse();
- assert course != null;
- commandLine.addParameter(new File(course.getResourcePath()).getParent());
- commandLine.addParameter(FileUtil.toSystemDependentName(executablePath));
- return commandLine.createProcess();
- }
+ private static void deleteWindowDescriptions(@NotNull final Task task, @NotNull final VirtualFile taskDir) {
+ for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+ String name = entry.getKey();
+ VirtualFile virtualFile = taskDir.findChild(name);
+ if (virtualFile == null) {
+ continue;
+ }
+ String windowsFileName = virtualFile.getNameWithoutExtension() + "_windows";
+ VirtualFile windowsFile = taskDir.findChild(windowsFileName);
+ if (windowsFile != null) {
+ StudyUtils.deleteFile(windowsFile);
}
- return null;
}
+ }
-
- String getPassedTests(Process p) {
- InputStream testOutput = p.getInputStream();
- BufferedReader testOutputReader = new BufferedReader(new InputStreamReader(testOutput));
- String line;
- try {
- while ((line = testOutputReader.readLine()) != null) {
- if (line.contains(TEST_FAILED)) {
- return line.substring(TEST_FAILED.length(), line.length());
- }
- }
- }
- catch (IOException e) {
- LOG.error(e);
+ private static void drawAllTaskWindows(@NotNull final Project project, @NotNull final Task task, @NotNull final VirtualFile taskDir) {
+ for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ VirtualFile virtualFile = taskDir.findChild(name);
+ if (virtualFile == null) {
+ continue;
}
- finally {
- StudyUtils.closeSilently(testOutputReader);
+ FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
+ if (fileEditor instanceof StudyEditor) {
+ StudyEditor studyEditor = (StudyEditor)fileEditor;
+ taskFile.drawAllWindows(studyEditor.getEditor());
}
- return TEST_OK;
}
}
+
public void check(@NotNull final Project project) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
@@ -107,188 +94,138 @@ public class StudyCheckAction extends DumbAwareAction {
CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
@Override
public void run() {
- final Editor selectedEditor = StudyEditor.getSelectedEditor(project);
- if (selectedEditor != null) {
- final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
- final VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
- if (openedFile != null) {
- StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
- final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
- List<VirtualFile> filesToDelete = new ArrayList<VirtualFile>();
- if (selectedTaskFile != null) {
- final VirtualFile taskDir = openedFile.getParent();
- Task currentTask = selectedTaskFile.getTask();
- StudyStatus oldStatus = currentTask.getStatus();
- Map<String, TaskFile> taskFiles = selectedTaskFile.getTask().getTaskFiles();
+ final StudyEditor selectedEditor = StudyEditor.getSelectedStudyEditor(project);
+ final StudyState studyState = new StudyState(selectedEditor);
+ if (!studyState.isValid()) {
+ LOG.error("StudyCheckAction was invokes outside study editor");
+ return;
+ }
+ Task task = studyState.getTask();
+ StudyStatus oldStatus = task.getStatus();
+ Map<String, TaskFile> taskFiles = task.getTaskFiles();
+ VirtualFile taskDir = studyState.getTaskDir();
+ flushWindows(task, taskDir);
+ StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID);
+ if (runAction != null && taskFiles.size() == 1) {
+ runAction.run(project);
+ }
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ IdeFocusManager.getInstance(project).requestFocus(studyState.getEditor().getComponent(), true);
+ }
+ });
+ final StudyTestRunner testRunner = new StudyTestRunner(task, taskDir);
+ Process testProcess = null;
+ try {
+ testProcess = testRunner.launchTests(project, studyState.getVirtualFile().getPath());
+ }
+ catch (ExecutionException e) {
+ LOG.error(e);
+ }
+ if (testProcess == null) {
+ return;
+ }
+ String failedMessage = testRunner.getPassedTests(testProcess);
+ if (failedMessage.equals(StudyTestRunner.TEST_OK)) {
+ task.setStatus(StudyStatus.Solved, oldStatus);
+ createTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), project);
+ }
+ else {
+ task.setStatus(StudyStatus.Failed, oldStatus);
for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
String name = entry.getKey();
TaskFile taskFile = entry.getValue();
- VirtualFile virtualFile = taskDir.findChild(name);
- if (virtualFile == null) {
+ if (taskFile.getTaskWindows().size() < 2) {
+ taskFile.setStatus(StudyStatus.Failed, StudyStatus.Unchecked);
continue;
}
- VirtualFile windowFile = StudyUtils.flushWindows(FileDocumentManager.getInstance().getDocument(virtualFile), taskFile, virtualFile);
- filesToDelete.add(windowFile);
- FileDocumentManager.getInstance().saveAllDocuments();
- }
-
- StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID);
- if (runAction != null && currentTask.getTaskFiles().size() == 1) {
- runAction.run(project);
- }
- final StudyTestRunner testRunner = new StudyTestRunner(currentTask, taskDir);
- Process testProcess = null;
- try {
- testProcess = testRunner.launchTests(project, openedFile.getPath());
- }
- catch (ExecutionException e) {
- LOG.error(e);
- }
- if (testProcess != null) {
- String failedMessage = testRunner.getPassedTests(testProcess);
- if (failedMessage.equals(StudyTestRunner.TEST_OK)) {
- currentTask.setStatus(StudyStatus.Solved, oldStatus);
- StudyUtils.updateStudyToolWindow(project);
- selectedTaskFile.drawAllWindows(selectedEditor);
- ProjectView.getInstance(project).refresh();
- for (VirtualFile file:filesToDelete) {
- try {
- file.delete(this);
- }
- catch (IOException e) {
- LOG.error(e);
- }
- }
- createTestResultPopUp("Congratulations!", JBColor.GREEN, project);
- return;
- }
- for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
- String name = entry.getKey();
- TaskFile taskFile = entry.getValue();
- TaskFile answerTaskFile = new TaskFile();
- VirtualFile virtualFile = taskDir.findChild(name);
- if (virtualFile == null) {
- continue;
- }
- VirtualFile answerFile = getCopyWithAnswers(taskDir, virtualFile, taskFile, answerTaskFile);
- for (TaskWindow taskWindow : answerTaskFile.getTaskWindows()) {
- Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
- if (document == null) {
- continue;
- }
- if (!taskWindow.isValid(document)) {
- continue;
- }
- check(project, taskWindow, answerFile, answerTaskFile, taskFile, document, testRunner, virtualFile);
- }
- FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
- Editor editor = null;
- if (fileEditor instanceof StudyEditor) {
- StudyEditor studyEditor = (StudyEditor) fileEditor;
- editor = studyEditor.getEditor();
- }
-
- if (editor != null) {
- taskFile.drawAllWindows(editor);
- StudyUtils.synchronize();
- }
- try {
- answerFile.delete(this);
- }
- catch (IOException e) {
- LOG.error(e);
- }
- }
- for (VirtualFile file:filesToDelete) {
- try {
- file.delete(this);
- }
- catch (IOException e) {
- LOG.error(e);
- }
- }
- currentTask.setStatus(StudyStatus.Failed, oldStatus);
- StudyUtils.updateStudyToolWindow(project);
- createTestResultPopUp(failedMessage, JBColor.RED, project);
+ runSmartTestProcess(taskDir, testRunner, name, taskFile, project);
}
+ createTestResultPopUp(failedMessage, MessageType.ERROR.getPopupBackground(), project);
+ navigateToFailedTaskWindow(studyState, task, taskDir, project);
}
+ StudyUtils.updateStudyToolWindow(project);
+ drawAllTaskWindows(project, task, taskDir);
+ ProjectView.getInstance(project).refresh();
+ deleteWindowDescriptions(task, taskDir);
}
- }
-
- }
- });
+ });
}
});
}
- private void check(Project project,
- TaskWindow taskWindow,
- VirtualFile answerFile,
- TaskFile answerTaskFile,
- TaskFile usersTaskFile,
- Document usersDocument,
- StudyTestRunner testRunner,
- VirtualFile openedFile) {
-
- try {
- VirtualFile windowCopy = answerFile.copy(this, answerFile.getParent(), answerFile.getNameWithoutExtension() + "_window" + taskWindow.getIndex() + ".py");
- final FileDocumentManager documentManager = FileDocumentManager.getInstance();
- final Document windowDocument = documentManager.getDocument(windowCopy);
- if (windowDocument != null) {
- StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
- Course course = taskManager.getCourse();
- Task task = usersTaskFile.getTask();
- int taskNum = task.getIndex() + 1;
- int lessonNum = task.getLesson().getIndex() + 1;
- assert course != null;
- String pathToResource = FileUtil.join(new File(course.getResourcePath()).getParent(), Lesson.LESSON_DIR + lessonNum, Task.TASK_DIR + taskNum);
- File resourceFile = new File(pathToResource, windowCopy.getName());
- FileUtil.copy(new File(pathToResource, openedFile.getName()), resourceFile);
- TaskFile windowTaskFile = new TaskFile();
- TaskFile.copy(answerTaskFile, windowTaskFile);
- StudyDocumentListener listener = new StudyDocumentListener(windowTaskFile);
- windowDocument.addDocumentListener(listener);
- int start = taskWindow.getRealStartOffset(windowDocument);
- int end = start + taskWindow.getLength();
- TaskWindow userTaskWindow = usersTaskFile.getTaskWindows().get(taskWindow.getIndex());
- int userStart = userTaskWindow.getRealStartOffset(usersDocument);
- int userEnd = userStart + userTaskWindow.getLength();
- String text = usersDocument.getText(new TextRange(userStart, userEnd));
- windowDocument.replaceString(start, end, text);
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- @Override
- public void run() {
- documentManager.saveDocument(windowDocument);
+ private static void navigateToFailedTaskWindow(@NotNull final StudyState studyState,
+ @NotNull final Task task,
+ @NotNull final VirtualFile taskDir,
+ @NotNull final Project project) {
+ TaskFile selectedTaskFile = studyState.getTaskFile();
+ Editor editor = studyState.getEditor();
+ TaskFile taskFileToNavigate = selectedTaskFile;
+ VirtualFile fileToNavigate = studyState.getVirtualFile();
+ if (!selectedTaskFile.hasFailedTaskWindows()) {
+ for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ if (taskFile.hasFailedTaskWindows()) {
+ taskFileToNavigate = taskFile;
+ VirtualFile virtualFile = taskDir.findChild(name);
+ if (virtualFile == null) {
+ continue;
}
- });
- VirtualFile fileWindows = StudyUtils.flushWindows(windowDocument, windowTaskFile, windowCopy);
- Process smartTestProcess = testRunner.launchTests(project, windowCopy.getPath());
- boolean res = testRunner.getPassedTests(smartTestProcess).equals(StudyTestRunner.TEST_OK);
- userTaskWindow.setStatus(res ? StudyStatus.Solved : StudyStatus.Failed, StudyStatus.Unchecked);
- windowCopy.delete(this);
- fileWindows.delete(this);
- if (!resourceFile.delete()) {
- LOG.error("failed to delete", resourceFile.getPath());
+ FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
+ if (fileEditor instanceof StudyEditor) {
+ StudyEditor studyEditor = (StudyEditor)fileEditor;
+ editor = studyEditor.getEditor();
+ }
+ fileToNavigate = virtualFile;
+ break;
}
}
}
- catch (IOException e) {
- LOG.error(e);
+ FileEditorManager.getInstance(project).openFile(fileToNavigate, true);
+ final Editor editorToNavigate = editor;
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ IdeFocusManager.getInstance(project).requestFocus(editorToNavigate.getContentComponent(), true);
+ }
+ });
+ taskFileToNavigate.navigateToFirstFailedTaskWindow(editor);
+ }
+
+ private void runSmartTestProcess(@NotNull final VirtualFile taskDir,
+ @NotNull final StudyTestRunner testRunner,
+ final String taskFileName,
+ @NotNull final TaskFile taskFile,
+ @NotNull final Project project) {
+ TaskFile answerTaskFile = new TaskFile();
+ VirtualFile virtualFile = taskDir.findChild(taskFileName);
+ if (virtualFile == null) {
+ return;
}
- catch (ExecutionException e) {
- LOG.error(e);
+ VirtualFile answerFile = getCopyWithAnswers(taskDir, virtualFile, taskFile, answerTaskFile);
+ for (TaskWindow taskWindow : answerTaskFile.getTaskWindows()) {
+ Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
+ if (document == null) {
+ continue;
+ }
+ if (!taskWindow.isValid(document)) {
+ continue;
+ }
+ taskWindow.smartCheck(project, answerFile, answerTaskFile, taskFile, testRunner, virtualFile, document);
}
+ StudyUtils.deleteFile(answerFile);
}
-
- private VirtualFile getCopyWithAnswers(final VirtualFile taskDir,
- final VirtualFile file,
- final TaskFile source,
- TaskFile target) {
+ private VirtualFile getCopyWithAnswers(@NotNull final VirtualFile taskDir,
+ @NotNull final VirtualFile file,
+ @NotNull final TaskFile source,
+ @NotNull final TaskFile target) {
VirtualFile copy = null;
try {
- copy = file.copy(this, taskDir, file.getNameWithoutExtension() +"_answers.py");
+ copy = file.copy(this, taskDir, file.getNameWithoutExtension() + ANSWERS_POSTFIX);
final FileDocumentManager documentManager = FileDocumentManager.getInstance();
final Document document = documentManager.getDocument(copy);
if (document != null) {
@@ -315,19 +252,18 @@ public class StudyCheckAction extends DumbAwareAction {
catch (IOException e) {
LOG.error(e);
}
-
-
return copy;
}
private static void createTestResultPopUp(final String text, Color color, @NotNull final Project project) {
BalloonBuilder balloonBuilder =
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, color, null);
- Balloon balloon = balloonBuilder.createBalloon();
+ final Balloon balloon = balloonBuilder.createBalloon();
StudyEditor studyEditor = StudyEditor.getSelectedStudyEditor(project);
assert studyEditor != null;
JButton checkButton = studyEditor.getCheckButton();
balloon.showInCenterOf(checkButton);
+ Disposer.register(project, balloon);
}
@Override
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java
index 5b9a6fef23ac..72660fc9c2c6 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java
@@ -19,6 +19,7 @@ import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.TabsListener;
import com.intellij.ui.tabs.impl.JBEditorTabs;
+import com.intellij.util.PlatformIcons;
import com.jetbrains.python.edu.StudyTaskManager;
import com.jetbrains.python.edu.StudyUtils;
import com.jetbrains.python.edu.course.Task;
@@ -26,7 +27,6 @@ import com.jetbrains.python.edu.course.TaskFile;
import com.jetbrains.python.edu.course.UserTest;
import com.jetbrains.python.edu.editor.StudyEditor;
import com.jetbrains.python.edu.ui.StudyTestContentPanel;
-import icons.StudyIcons;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -92,7 +92,7 @@ public class StudyEditInputAction extends DumbAwareAction {
i++;
}
TabInfo plusTab = new TabInfo(new JPanel());
- plusTab.setIcon(StudyIcons.Add);
+ plusTab.setIcon(PlatformIcons.ADD_ICON);
tabbedPane.addTabSilently(plusTab, tabbedPane.getTabCount());
final JBPopup hint =
JBPopupFactory.getInstance().createComponentPopupBuilder(tabbedPane.getComponent(), tabbedPane.getComponent())
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java
new file mode 100644
index 000000000000..0b75c4bc01c4
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java
@@ -0,0 +1,34 @@
+/*
+ * 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 com.jetbrains.python.edu.actions;
+
+import com.jetbrains.python.edu.StudyDirectoryProjectGenerator;
+import com.jetbrains.python.newProject.actions.GenerateProjectCallback;
+import com.jetbrains.python.newProject.actions.ProjectSpecificAction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class StudyNewProject extends ProjectSpecificAction {
+
+ public StudyNewProject(@NotNull final String name, @Nullable final Runnable runnable) {
+ super(new GenerateProjectCallback(runnable), new StudyDirectoryProjectGenerator(), name, true);
+ }
+
+ public StudyNewProject() {
+ this("Learn Python", null);
+ }
+
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java
index 81818a95c044..3c971c3fe15e 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java
@@ -2,13 +2,14 @@ package com.jetbrains.python.edu.actions;
import com.jetbrains.python.edu.editor.StudyEditor;
import com.jetbrains.python.edu.course.Task;
+import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class StudyNextStudyTaskAction extends StudyTaskNavigationAction {
@Override
- protected JButton getButton(StudyEditor selectedStudyEditor) {
+ protected JButton getButton(@NotNull final StudyEditor selectedStudyEditor) {
return selectedStudyEditor.getNextTaskButton();
}
@@ -18,7 +19,7 @@ public class StudyNextStudyTaskAction extends StudyTaskNavigationAction {
}
@Override
- protected Task getTargetTask(Task sourceTask) {
+ protected Task getTargetTask(@NotNull final Task sourceTask) {
return sourceTask.next();
}
} \ No newline at end of file
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java
index 595aeeff42e3..fcf9ef40c7d4 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java
@@ -1,8 +1,8 @@
package com.jetbrains.python.edu.actions;
+import com.intellij.icons.AllIcons;
import com.jetbrains.python.edu.StudyUtils;
import com.jetbrains.python.edu.course.TaskWindow;
-import icons.StudyIcons;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -16,7 +16,7 @@ public class StudyNextWindowAction extends StudyWindowNavigationAction {
public static final String SHORTCUT2 = "ctrl pressed ENTER";
public StudyNextWindowAction() {
- super("NextWindowAction", "Select next window", StudyIcons.Next);
+ super("NextWindowAction", "Select next window", AllIcons.Actions.Forward);
}
@Override
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java
index bc26c28cfabd..f6da6a067894 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java
@@ -3,13 +3,14 @@ package com.jetbrains.python.edu.actions;
import com.jetbrains.python.edu.editor.StudyEditor;
import com.jetbrains.python.edu.course.Task;
+import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class StudyPreviousStudyTaskAction extends StudyTaskNavigationAction {
@Override
- protected JButton getButton(StudyEditor selectedStudyEditor) {
+ protected JButton getButton(@NotNull final StudyEditor selectedStudyEditor) {
return selectedStudyEditor.getPrevTaskButton();
}
@@ -19,7 +20,7 @@ public class StudyPreviousStudyTaskAction extends StudyTaskNavigationAction {
}
@Override
- protected Task getTargetTask(Task sourceTask) {
+ protected Task getTargetTask(@NotNull final Task sourceTask) {
return sourceTask.prev();
}
} \ No newline at end of file
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java
index f8abb0b63365..a9448ddea0e3 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java
@@ -14,6 +14,7 @@ import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.jetbrains.python.edu.StudyDocumentListener;
@@ -24,8 +25,8 @@ import com.jetbrains.python.edu.editor.StudyEditor;
import java.io.*;
-public class StudyRefreshTaskAction extends DumbAwareAction {
- private static final Logger LOG = Logger.getInstance(StudyRefreshTaskAction.class.getName());
+public class StudyRefreshTaskFileAction extends DumbAwareAction {
+ private static final Logger LOG = Logger.getInstance(StudyRefreshTaskFileAction.class.getName());
public void refresh(final Project project) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@@ -92,14 +93,20 @@ public class StudyRefreshTaskAction extends DumbAwareAction {
document.addDocumentListener(listener);
}
selectedTaskFile.drawAllWindows(editor);
- IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true);
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true);
+ }
+ });
selectedTaskFile.navigateToFirstTaskWindow(editor);
BalloonBuilder balloonBuilder =
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can now start again", MessageType.INFO, null);
- Balloon balloon = balloonBuilder.createBalloon();
+ final Balloon balloon = balloonBuilder.createBalloon();
StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
assert selectedStudyEditor != null;
balloon.showInCenterOf(selectedStudyEditor.getRefreshButton());
+ Disposer.register(project, balloon);
}
catch (FileNotFoundException e1) {
LOG.error(e1);
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java
index 1efa90889449..2952486274cf 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java
@@ -5,19 +5,18 @@ import com.intellij.codeInsight.documentation.DocumentationManager;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
-import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.util.Disposer;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
+import com.jetbrains.python.edu.StudyState;
import com.jetbrains.python.edu.StudyTaskManager;
import com.jetbrains.python.edu.StudyUtils;
import com.jetbrains.python.edu.course.Course;
-import com.jetbrains.python.edu.course.TaskFile;
import com.jetbrains.python.edu.course.TaskWindow;
import com.jetbrains.python.edu.editor.StudyEditor;
import icons.StudyIcons;
@@ -33,58 +32,56 @@ public class StudyShowHintAction extends DumbAwareAction {
}
public void actionPerformed(AnActionEvent e) {
- Project project = e.getProject();
- if (project != null) {
+ final Project project = e.getProject();
+ if (project == null) {
+ return;
+ }
+ Course course = StudyTaskManager.getInstance(project).getCourse();
+ if (course == null) {
+ return;
+ }
+ StudyState studyState = new StudyState(StudyEditor.getSelectedStudyEditor(project));
+ if (!studyState.isValid()) {
+ return;
+ }
+ PsiFile file = PsiManager.getInstance(project).findFile(studyState.getVirtualFile());
+ final Editor editor = studyState.getEditor();
+ LogicalPosition pos = editor.getCaretModel().getLogicalPosition();
+ TaskWindow taskWindow = studyState.getTaskFile().getTaskWindow(editor.getDocument(), pos);
+ if (file == null || taskWindow == null) {
+ return;
+ }
+ String hint = taskWindow.getHint();
+ if (hint == null) {
+ return;
+ }
+ File resourceFile = new File(course.getResourcePath());
+ File resourceRoot = resourceFile.getParentFile();
+ if (resourceRoot == null || !resourceRoot.exists()) {
+ return;
+ }
+ File hintsDir = new File(resourceRoot, Course.HINTS_DIR);
+ if (hintsDir.exists()) {
+ String hintText = StudyUtils.getFileText(hintsDir.getAbsolutePath(), hint, true);
+ int offset = editor.getDocument().getLineStartOffset(pos.line) + pos.column;
+ PsiElement element = file.findElementAt(offset);
+ if (hintText == null || element == null) {
+ return;
+ }
+
DocumentationManager documentationManager = DocumentationManager.getInstance(project);
DocumentationComponent component = new DocumentationComponent(documentationManager);
- Editor selectedEditor = StudyEditor.getSelectedEditor(project);
- FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
- assert selectedEditor != null;
- VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
- if (openedFile != null) {
- StudyTaskManager taskManager = StudyTaskManager.getInstance(e.getProject());
- TaskFile taskFile = taskManager.getTaskFile(openedFile);
- if (taskFile != null) {
- PsiFile file = PsiManager.getInstance(project).findFile(openedFile);
- if (file != null) {
- LogicalPosition pos = selectedEditor.getCaretModel().getLogicalPosition();
- TaskWindow taskWindow = taskFile.getTaskWindow(selectedEditor.getDocument(), pos);
- if (taskWindow != null) {
- String hint = taskWindow.getHint();
- if (hint == null) {
- return;
- }
- Course course = taskManager.getCourse();
- if (course != null) {
- File resourceFile = new File(course.getResourcePath());
- File resourceRoot = resourceFile.getParentFile();
- if (resourceRoot != null && resourceRoot.exists()) {
- File hintsDir = new File(resourceRoot, Course.HINTS_DIR);
- if (hintsDir.exists()) {
- String hintText = StudyUtils.getFileText(hintsDir.getAbsolutePath(), hint, true);
- if (hintText != null) {
- int offset = selectedEditor.getDocument().getLineStartOffset(pos.line) + pos.column;
- PsiElement element = file.findElementAt(offset);
- if (element != null) {
- component.setData(element, hintText, true, null);
- final JBPopup popup =
- JBPopupFactory.getInstance().createComponentPopupBuilder(component, component)
- .setDimensionServiceKey(project, DocumentationManager.JAVADOC_LOCATION_AND_SIZE, false)
- .setResizable(true)
- .setMovable(true)
- .setRequestFocus(true)
- .createPopup();
- component.setHint(popup);
- popup.showInBestPositionFor(selectedEditor);
- }
- }
- }
- }
- }
- }
- }
- }
- }
+ component.setData(element, hintText, true, null);
+ final JBPopup popup =
+ JBPopupFactory.getInstance().createComponentPopupBuilder(component, component)
+ .setDimensionServiceKey(project, DocumentationManager.JAVADOC_LOCATION_AND_SIZE, false)
+ .setResizable(true)
+ .setMovable(true)
+ .setRequestFocus(true)
+ .createPopup();
+ component.setHint(popup);
+ popup.showInBestPositionFor(editor);
+ Disposer.dispose(component);
}
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
index b781e7da8849..46c0981cb964 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
@@ -1,8 +1,6 @@
package com.jetbrains.python.edu.actions;
import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
@@ -11,37 +9,34 @@ import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.BalloonBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.python.edu.StudyTaskManager;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowId;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.jetbrains.python.edu.StudyState;
import com.jetbrains.python.edu.course.Lesson;
import com.jetbrains.python.edu.course.Task;
import com.jetbrains.python.edu.course.TaskFile;
import com.jetbrains.python.edu.editor.StudyEditor;
+import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.Map;
-/**
- * author: liana
- * data: 7/21/14.
- */
+
abstract public class StudyTaskNavigationAction extends DumbAwareAction {
- public void navigateTask(Project project) {
- Editor selectedEditor = StudyEditor.getSelectedEditor(project);
- FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
- assert selectedEditor != null;
- VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
- StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
- assert openedFile != null;
- TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
- assert selectedTaskFile != null;
- Task currentTask = selectedTaskFile.getTask();
- Task nextTask = getTargetTask(currentTask);
+ public void navigateTask(@NotNull final Project project) {
+ StudyEditor studyEditor = StudyEditor.getSelectedStudyEditor(project);
+ StudyState studyState = new StudyState(studyEditor);
+ if (!studyState.isValid()) {
+ return;
+ }
+ Task nextTask = getTargetTask(studyState.getTask());
if (nextTask == null) {
BalloonBuilder balloonBuilder =
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(getNavigationFinishedMessage(), MessageType.INFO, null);
Balloon balloon = balloonBuilder.createBalloon();
- StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
- balloon.showInCenterOf(getButton(selectedStudyEditor));
+ assert studyEditor != null;
+ balloon.showInCenterOf(getButton(studyEditor));
return;
}
for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
@@ -82,16 +77,24 @@ abstract public class StudyTaskNavigationAction extends DumbAwareAction {
if (shouldBeActive != null) {
FileEditorManager.getInstance(project).openFile(shouldBeActive, true);
}
+ ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN);
+ if (runToolWindow != null) {
+ runToolWindow.hide(null);
+ }
}
- protected abstract JButton getButton(StudyEditor selectedStudyEditor);
+ protected abstract JButton getButton(@NotNull final StudyEditor selectedStudyEditor);
@Override
public void actionPerformed(AnActionEvent e) {
- navigateTask(e.getProject());
+ Project project = e.getProject();
+ if (project == null) {
+ return;
+ }
+ navigateTask(project);
}
protected abstract String getNavigationFinishedMessage();
- protected abstract Task getTargetTask(Task sourceTask);
+ protected abstract Task getTargetTask(@NotNull final Task sourceTask);
}
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
index 4f17fc0d27f3..c46c4f56f937 100644
--- 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
@@ -21,7 +21,7 @@ import java.util.List;
* which is visible to student in project view
*/
-public class TaskFile implements Stateful{
+public class TaskFile implements Stateful {
public List<TaskWindow> taskWindows = new ArrayList<TaskWindow>();
private Task myTask;
@Transient
@@ -173,7 +173,18 @@ public class TaskFile implements Stateful{
for (TaskWindow w : taskWindows) {
if ((w.getLine() == line) && (w.getStart() >= oldEndOffsetInLine)) {
int distance = w.getStart() - oldEndOffsetInLine;
- if (lineChange != 0 || newEndOffsetInLine <= w.getStart()) {
+ 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);
}
@@ -217,12 +228,33 @@ public class TaskFile implements Stateful{
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);
+ 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;
}
}
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
index 4fb112cc1f9b..dc4a75a800ce 100644
--- 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
@@ -1,5 +1,8 @@
package com.jetbrains.python.edu.course;
+import com.intellij.execution.ExecutionException;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
@@ -8,16 +11,27 @@ 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.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JBColor;
+import com.jetbrains.python.edu.StudyDocumentListener;
+import com.jetbrains.python.edu.StudyTestRunner;
+import com.jetbrains.python.edu.StudyUtils;
import org.jetbrains.annotations.NotNull;
+import java.io.File;
+import java.io.IOException;
+
/**
* Implementation of windows which user should type in
*/
public class TaskWindow implements Comparable, Stateful {
-
+ private static final String WINDOW_POSTFIX = "_window.py";
+ private static final Logger LOG = Logger.getInstance(TaskWindow.class);
public int line = 0;
public int start = 0;
public String hint = "";
@@ -174,4 +188,55 @@ public class TaskWindow implements Comparable, Stateful {
public int getIndex() {
return myIndex;
}
+
+ public void smartCheck(@NotNull final Project project,
+ @NotNull final VirtualFile answerFile,
+ @NotNull final TaskFile answerTaskFile,
+ @NotNull final TaskFile usersTaskFile,
+ @NotNull final StudyTestRunner testRunner,
+ @NotNull final VirtualFile virtualFile,
+ @NotNull final Document usersDocument) {
+
+ try {
+ VirtualFile windowCopy =
+ answerFile.copy(this, answerFile.getParent(), answerFile.getNameWithoutExtension() + WINDOW_POSTFIX);
+ final FileDocumentManager documentManager = FileDocumentManager.getInstance();
+ final Document windowDocument = documentManager.getDocument(windowCopy);
+ if (windowDocument != null) {
+ File resourceFile = StudyUtils.copyResourceFile(virtualFile.getName(), windowCopy.getName(), project, usersTaskFile.getTask());
+ TaskFile windowTaskFile = new TaskFile();
+ TaskFile.copy(answerTaskFile, windowTaskFile);
+ StudyDocumentListener listener = new StudyDocumentListener(windowTaskFile);
+ windowDocument.addDocumentListener(listener);
+ int start = getRealStartOffset(windowDocument);
+ int end = start + getLength();
+ TaskWindow userTaskWindow = usersTaskFile.getTaskWindows().get(getIndex());
+ int userStart = userTaskWindow.getRealStartOffset(usersDocument);
+ int userEnd = userStart + userTaskWindow.getLength();
+ String text = usersDocument.getText(new TextRange(userStart, userEnd));
+ windowDocument.replaceString(start, end, text);
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ documentManager.saveDocument(windowDocument);
+ }
+ });
+ VirtualFile fileWindows = StudyUtils.flushWindows(windowTaskFile, windowCopy);
+ Process smartTestProcess = testRunner.launchTests(project, windowCopy.getPath());
+ boolean res = testRunner.getPassedTests(smartTestProcess).equals(StudyTestRunner.TEST_OK);
+ userTaskWindow.setStatus(res ? StudyStatus.Solved : StudyStatus.Failed, StudyStatus.Unchecked);
+ StudyUtils.deleteFile(windowCopy);
+ StudyUtils.deleteFile(fileWindows);
+ if (!resourceFile.delete()) {
+ LOG.error("failed to delete", resourceFile.getPath());
+ }
+ }
+ }
+ catch (ExecutionException e) {
+ LOG.error(e);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
} \ No newline at end of file
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java
index 69c5acc5f127..6b27c4a406db 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java
@@ -1,8 +1,10 @@
package com.jetbrains.python.edu.editor;
import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
+import com.intellij.icons.AllIcons;
import com.intellij.ide.structureView.StructureViewBuilder;
import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
@@ -17,9 +19,15 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.IdeFocusManager;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowId;
+import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.pom.Navigatable;
+import com.intellij.ui.BrowserHyperlinkListener;
import com.intellij.ui.HideableTitledPanel;
import com.intellij.ui.JBColor;
+import com.intellij.util.ui.EmptyClipboardOwner;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.python.edu.StudyDocumentListener;
import com.jetbrains.python.edu.StudyTaskManager;
@@ -36,8 +44,8 @@ import javax.swing.text.MutableAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.*;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.*;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
@@ -50,12 +58,13 @@ public class StudyEditor implements TextEditor {
private static final String TASK_TEXT_HEADER = "Task Text";
private final FileEditor myDefaultEditor;
private final JComponent myComponent;
+ private final TaskFile myTaskFile;
private JButton myCheckButton;
private JButton myNextTaskButton;
private JButton myPrevTaskButton;
private JButton myRefreshButton;
private static final Map<Document, StudyDocumentListener> myDocumentListeners = new HashMap<Document, StudyDocumentListener>();
- private Project myProject;
+ private final Project myProject;
public JButton getCheckButton() {
return myCheckButton;
@@ -65,6 +74,10 @@ public class StudyEditor implements TextEditor {
return myPrevTaskButton;
}
+ public TaskFile getTaskFile() {
+ return myTaskFile;
+ }
+
private static JButton addButton(@NotNull final JComponent parentComponent, String toolTipText, Icon icon) {
JButton newButton = new JButton();
newButton.setToolTipText(toolTipText);
@@ -89,26 +102,72 @@ public class StudyEditor implements TextEditor {
myComponent = myDefaultEditor.getComponent();
JPanel studyPanel = new JPanel();
studyPanel.setLayout(new BoxLayout(studyPanel, BoxLayout.Y_AXIS));
- TaskFile taskFile = StudyTaskManager.getInstance(myProject).getTaskFile(file);
- if (taskFile != null) {
- Task currentTask = taskFile.getTask();
+ myTaskFile = StudyTaskManager.getInstance(myProject).getTaskFile(file);
+ if (myTaskFile != null) {
+ Task currentTask = myTaskFile.getTask();
String taskText = currentTask.getResourceText(project, currentTask.getText(), false);
initializeTaskText(studyPanel, taskText);
JPanel studyButtonPanel = new JPanel(new GridLayout(1, 2));
JPanel taskActionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
studyButtonPanel.add(taskActionsPanel);
studyButtonPanel.add(new JPanel());
- initializeButtons(taskActionsPanel, taskFile);
+ initializeButtons(taskActionsPanel, myTaskFile);
studyPanel.add(studyButtonPanel);
myComponent.add(studyPanel, BorderLayout.NORTH);
}
}
- private static void initializeTaskText(JPanel studyPanel, @Nullable String taskText) {
+ class CopyListener extends MouseAdapter {
+ final JTextPane myTextPane;
+
+ public CopyListener(JTextPane textPane) {
+ myTextPane = textPane;
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ToolWindow projectView = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW);
+ if (projectView == null) {
+ return;
+ }
+ final Component focusComponent = projectView.getComponent();
+ IdeFocusManager.getInstance(myProject).requestFocus(focusComponent, true);
+ final String text = myTextPane.getSelectedText();
+ if (text == null) {
+ return;
+ }
+ KeyAdapter keyAdapter = new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent ev) {
+ if (ev.getKeyCode() == KeyEvent.VK_C
+ && ev.getModifiers() == InputEvent.CTRL_MASK) {
+ StringSelection selection = new StringSelection(text);
+ Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, EmptyClipboardOwner.INSTANCE);
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ IdeFocusManager.getInstance(myProject).requestFocus(myDefaultEditor.getComponent(), true);
+ }
+ });
+ }
+ }
+ };
+ focusComponent.addKeyListener(keyAdapter);
+ }
+ });
+ }
+ }
+
+ private void initializeTaskText(JPanel studyPanel, @Nullable String taskText) {
JTextPane taskTextPane = new JTextPane();
+ taskTextPane.addMouseListener(new CopyListener(taskTextPane));
taskTextPane.setContentType("text/html");
taskTextPane.setEditable(false);
taskTextPane.setText(taskText);
+ taskTextPane.addHyperlinkListener(new BrowserHyperlinkListener());
EditorColorsScheme editorColorsScheme = EditorColorsManager.getInstance().getGlobalScheme();
int fontSize = editorColorsScheme.getEditorFontSize();
String fontName = editorColorsScheme.getEditorFontName();
@@ -134,10 +193,10 @@ public class StudyEditor implements TextEditor {
private void initializeButtons(@NotNull final JPanel taskActionsPanel, @NotNull final TaskFile taskFile) {
myCheckButton = addButton(taskActionsPanel, "Check task", StudyIcons.Resolve);
myPrevTaskButton = addButton(taskActionsPanel, "Prev Task", StudyIcons.Prev);
- myNextTaskButton = addButton(taskActionsPanel, "Next Task", StudyIcons.Next);
- myRefreshButton = addButton(taskActionsPanel, "Start task again", StudyIcons.Refresh24);
+ myNextTaskButton = addButton(taskActionsPanel, "Next Task", AllIcons.Actions.Forward);
+ myRefreshButton = addButton(taskActionsPanel, "Start task again", AllIcons.Actions.Refresh);
if (!taskFile.getTask().getUserTests().isEmpty()) {
- JButton runButton = addButton(taskActionsPanel, "Run", StudyIcons.Run);
+ JButton runButton = addButton(taskActionsPanel, "Run", AllIcons.General.Run);
runButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@@ -149,7 +208,8 @@ public class StudyEditor implements TextEditor {
watchInputButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- StudyEditInputAction studyEditInputAction = (StudyEditInputAction)ActionManager.getInstance().getAction("WatchInputAction");
+ StudyEditInputAction studyEditInputAction =
+ (StudyEditInputAction)ActionManager.getInstance().getAction("WatchInputAction");
studyEditInputAction.showInput(myProject);
}
});
@@ -165,7 +225,8 @@ public class StudyEditor implements TextEditor {
myNextTaskButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- StudyNextStudyTaskAction studyNextTaskAction = (StudyNextStudyTaskAction)ActionManager.getInstance().getAction("NextTaskAction");
+ StudyNextStudyTaskAction studyNextTaskAction =
+ (StudyNextStudyTaskAction)ActionManager.getInstance().getAction("NextTaskAction");
studyNextTaskAction.navigateTask(myProject);
}
});
@@ -180,7 +241,8 @@ public class StudyEditor implements TextEditor {
myRefreshButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- StudyRefreshTaskAction studyRefreshTaskAction = (StudyRefreshTaskAction)ActionManager.getInstance().getAction("RefreshTaskAction");
+ StudyRefreshTaskFileAction studyRefreshTaskAction =
+ (StudyRefreshTaskFileAction)ActionManager.getInstance().getAction("RefreshTaskAction");
studyRefreshTaskAction.refresh(myProject);
}
});
@@ -300,7 +362,8 @@ public class StudyEditor implements TextEditor {
if (fileEditor instanceof StudyEditor) {
return (StudyEditor)fileEditor;
}
- } catch (Exception e) {
+ }
+ catch (Exception e) {
return null;
}
return null;
@@ -325,8 +388,9 @@ public class StudyEditor implements TextEditor {
@NotNull
@Override
public Editor getEditor() {
- if (myDefaultEditor instanceof TextEditor)
+ if (myDefaultEditor instanceof TextEditor) {
return ((TextEditor)myDefaultEditor).getEditor();
+ }
return EditorFactory.getInstance().createViewer(new DocumentImpl(""), myProject);
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/highlighting/StudyVisitorFilter.java b/python/edu/learn-python/src/com/jetbrains/python/edu/highlighting/StudyVisitorFilter.java
new file mode 100644
index 000000000000..dc7495adb16e
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/highlighting/StudyVisitorFilter.java
@@ -0,0 +1,18 @@
+package com.jetbrains.python.edu.highlighting;
+
+import com.intellij.psi.PsiFile;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.inspections.PythonVisitorFilter;
+import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection;
+import org.jetbrains.annotations.NotNull;
+
+public class StudyVisitorFilter implements PythonVisitorFilter {
+ @Override
+ public boolean isSupported(@NotNull final Class visitorClass, @NotNull final PsiFile file) {
+ if (StudyTaskManager.getInstance(file.getProject()).getCourse() == null) return true;
+ if (visitorClass == PyUnresolvedReferencesInspection.class) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
index abf648c5c82a..2f80dba12695 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
@@ -32,7 +32,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
@Override
protected void updateImpl(PresentationData data) {
- data.setIcon(StudyIcons.Unchecked);
+ data.setIcon(StudyIcons.Task);
String valueName = myValue.getName();
StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(myProject);
Course course = studyTaskManager.getCourse();
@@ -41,7 +41,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
}
if (valueName.equals(myProject.getName())) {
data.clearText();
- data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, JBColor.BLUE));
+ data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, JBColor.BLACK));
data.addText(" (" + valueName + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES);
return;
}
@@ -91,15 +91,16 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
StudyStatus taskStatus = stateful.getStatus();
switch (taskStatus) {
case Unchecked: {
- updatePresentation(data, additionalName, JBColor.blue, StudyIcons.Unchecked);
+ updatePresentation(data, additionalName, JBColor.BLACK, stateful instanceof Lesson ? StudyIcons.Lesson : StudyIcons.Task);
break;
}
case Solved: {
- updatePresentation(data, additionalName, new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)), StudyIcons.Checked);
+ updatePresentation(data, additionalName, new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)),
+ stateful instanceof Lesson ? StudyIcons.LessonCompl : StudyIcons.TaskCompl);
break;
}
case Failed: {
- updatePresentation(data, additionalName, JBColor.RED, StudyIcons.Failed);
+ updatePresentation(data, additionalName, JBColor.RED, stateful instanceof Lesson ? StudyIcons.Lesson : StudyIcons.TaskProbl);
}
}
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form
index 133c38d4e8f8..8dd6506d710c 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form
@@ -39,11 +39,9 @@
</component>
</children>
</grid>
- <component id="6c40c" class="javax.swing.JLabel">
+ <component id="6c40c" class="javax.swing.JLabel" binding="myLabel">
<constraints>
- <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
- <preferred-size width="81" height="-1"/>
- </grid>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<font/>
@@ -67,9 +65,7 @@
</component>
<component id="f1e10" class="javax.swing.JButton" binding="myRefreshButton">
<constraints>
- <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
- <minimum-size width="30" height="23"/>
- </grid>
+ <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<hideActionText value="false"/>
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java
index 0f1ec08a8856..6edad63586f0 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java
@@ -2,6 +2,7 @@ package com.jetbrains.python.edu.ui;
import com.intellij.facet.ui.FacetValidatorsManager;
import com.intellij.facet.ui.ValidationResult;
+import com.intellij.icons.AllIcons;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.vfs.VirtualFile;
@@ -9,7 +10,6 @@ import com.intellij.util.Consumer;
import com.jetbrains.python.edu.StudyDirectoryProjectGenerator;
import com.jetbrains.python.edu.StudyUtils;
import com.jetbrains.python.edu.course.CourseInfo;
-import icons.StudyIcons;
import javax.swing.*;
import java.awt.event.ActionEvent;
@@ -32,6 +32,7 @@ public class StudyNewProjectPanel{
private JPanel myContentPanel;
private JLabel myAuthorLabel;
private JLabel myDescriptionLabel;
+ private JLabel myLabel;
private final StudyDirectoryProjectGenerator myGenerator;
private static final String CONNECTION_ERROR = "<html>Failed to download courses.<br>Check your Internet connection.</html>";
private static final String INVALID_COURSE = "Selected course is invalid";
@@ -56,7 +57,9 @@ public class StudyNewProjectPanel{
}
initListeners();
myRefreshButton.setVisible(true);
- myRefreshButton.setIcon(StudyIcons.Refresh);
+ myRefreshButton.setIcon(AllIcons.Actions.Refresh);
+
+ myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
}
private void initListeners() {
diff --git a/python/edu/main_pycharm_edu.iml b/python/edu/main_pycharm_edu.iml
index 12efe9b350d1..17f1d67a39da 100644
--- a/python/edu/main_pycharm_edu.iml
+++ b/python/edu/main_pycharm_edu.iml
@@ -16,6 +16,7 @@
<orderEntry type="module" module-name="ShortcutPromoter" />
<orderEntry type="module" module-name="python-educational" />
<orderEntry type="module" module-name="learn-python" />
+ <orderEntry type="module" module-name="course-creator" />
</component>
</module>
diff --git a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
index 0fdf0d48fd08..eed232dcd3c7 100644
--- a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
+++ b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
@@ -19,5 +19,5 @@
<feedback eap-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&amp;build=$BUILD&amp;timezone=$TIMEZONE&amp;eval=$EVAL"
release-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&amp;build=$BUILD&amp;timezone=$TIMEZONE&amp;eval=$EVAL"/>
- <help file="pycharmhelp.jar" root="pycharm"/>
+ <help file="pycharm-eduhelp.jar" root="pycharm"/>
</component>
diff --git a/python/edu/src/META-INF/PyCharmEduPlugin.xml b/python/edu/src/META-INF/PyCharmEduPlugin.xml
index d5b2fdfe8c28..87739a948968 100644
--- a/python/edu/src/META-INF/PyCharmEduPlugin.xml
+++ b/python/edu/src/META-INF/PyCharmEduPlugin.xml
@@ -19,6 +19,10 @@
</component>
</application-components>
+ <extensions defaultExtensionNs="com.intellij">
+ <codeInsight.lineMarkerProvider language="Python" implementationClass="com.jetbrains.python.edu.PyExecuteFileLineMarkerProvider"/>
+ </extensions>
+
<actions>
<group overrides="true" class="com.intellij.openapi.actionSystem.EmptyActionGroup" id="ToolsMenu"/>
@@ -38,5 +42,11 @@
<action overrides="true" class="com.intellij.openapi.actionSystem.EmptyAction" id="NewHtmlFile"/>
+ <group id="PyRunMenu">
+ <action id="runCurrentFile" class="com.jetbrains.python.edu.PyRunCurrentFileAction"/>
+ <add-to-group group-id="RunMenu" anchor="first"/>
+ </group>
+
+
</actions>
</idea-plugin>
diff --git a/python/edu/src/com/intellij/openapi/application/PyCharmEduConfigImportSettings.java b/python/edu/src/com/intellij/openapi/application/PyCharmEduConfigImportSettings.java
new file mode 100644
index 000000000000..989c750681f3
--- /dev/null
+++ b/python/edu/src/com/intellij/openapi/application/PyCharmEduConfigImportSettings.java
@@ -0,0 +1,12 @@
+package com.intellij.openapi.application;
+
+import com.intellij.ide.plugins.PluginManagerCore;
+
+// see com.intellij.openapi.application.ConfigImportHelper.getConfigImportSettings
+@SuppressWarnings("UnusedDeclaration")
+public class PyCharmEduConfigImportSettings extends ConfigImportSettings {
+ public PyCharmEduConfigImportSettings() {
+ PluginManagerCore.disablePlugin("org.jetbrains.plugins.coursecreator");
+ }
+
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
index ecf3d79a6424..2954f7c5b706 100644
--- a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
+++ b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
@@ -35,14 +35,15 @@ import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
import com.intellij.openapi.keymap.impl.KeymapImpl;
+import com.intellij.openapi.project.DumbAwareRunnable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerAdapter;
import com.intellij.openapi.project.ex.ProjectManagerEx;
+import com.intellij.openapi.startup.StartupManager;
+import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.wm.ToolWindowEP;
-import com.intellij.openapi.wm.ToolWindowId;
-import com.intellij.openapi.wm.WindowManager;
+import com.intellij.openapi.wm.*;
import com.intellij.platform.DirectoryProjectConfigurator;
import com.intellij.platform.PlatformProjectViewOpener;
import com.intellij.psi.codeStyle.CodeStyleSettings;
@@ -62,9 +63,9 @@ import java.util.Set;
*/
@SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "UtilityClassWithPublicConstructor"})
public class PyCharmEduInitialConfigurator {
- @NonNls private static final String DISPLAYED_PROPERTY = "PyCharm.initialConfigurationShown";
+ @NonNls private static final String DISPLAYED_PROPERTY = "PyCharmEDU.initialConfigurationShown";
- @NonNls private static final String CONFIGURED = "PyCharm.InitialConfiguration";
+ @NonNls private static final String CONFIGURED = "PyCharmEDU.InitialConfiguration";
public static class First {
@@ -93,6 +94,8 @@ public class PyCharmEduInitialConfigurator {
uiSettings.SHOW_MAIN_TOOLBAR = false;
codeInsightSettings.REFORMAT_ON_PASTE = CodeInsightSettings.NO_REFORMAT;
+ Registry.get("ide.new.settings.dialog").setValue(true);
+
GeneralSettings.getInstance().setShowTipsOnStartup(false);
EditorSettingsExternalizable.getInstance().setVirtualSpace(false);
@@ -113,7 +116,7 @@ public class PyCharmEduInitialConfigurator {
});
}
});
- PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP = true;
+ PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP = false;
}
if (!propertiesComponent.isValueSet(DISPLAYED_PROPERTY)) {
@@ -147,6 +150,29 @@ public class PyCharmEduInitialConfigurator {
}
patchProjectAreaExtensions(project);
+
+ StartupManager.getInstance(project).runWhenProjectIsInitialized(new DumbAwareRunnable() {
+ @Override
+ public void run() {
+ if (project.isDisposed()) return;
+
+ ToolWindowManager.getInstance(project).invokeLater(new Runnable() {
+ int count = 0;
+
+ public void run() {
+ if (project.isDisposed()) return;
+ if (count++ < 3) { // we need to call this after ToolWindowManagerImpl.registerToolWindowsFromBeans
+ ToolWindowManager.getInstance(project).invokeLater(this);
+ return;
+ }
+ ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow("Project");
+ if (toolWindow.getType() != ToolWindowType.SLIDING) {
+ toolWindow.activate(null);
+ }
+ }
+ });
+ }
+ });
}
});
}
@@ -205,6 +231,11 @@ public class PyCharmEduInitialConfigurator {
private static void showInitialConfigurationDialog() {
final JFrame frame = WindowManager.getInstance().findVisibleFrame();
- new InitialConfigurationDialog(frame, "Python").show();
+ new InitialConfigurationDialog(frame, "Python") {
+ @Override
+ protected boolean canCreateLauncherScript() {
+ return false;
+ }
+ }.show();
}
}
diff --git a/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
new file mode 100644
index 000000000000..03522bb8cf8a
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java
@@ -0,0 +1,91 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.codeHighlighting.Pass;
+import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
+import com.intellij.codeInsight.daemon.LineMarkerInfo;
+import com.intellij.codeInsight.daemon.LineMarkerProvider;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.markup.GutterIconRenderer;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.psi.util.PsiUtilBase;
+import com.intellij.util.Function;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyImportStatement;
+import com.jetbrains.python.psi.PyStatement;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.event.MouseEvent;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author traff
+ */
+public class PyExecuteFileLineMarkerProvider implements LineMarkerProvider {
+ @Nullable
+ @Override
+ public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
+ return null;
+ }
+
+ @Override
+ public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
+ for (PsiElement element : elements) {
+ if (isFirstCodeLine(element)) {
+ result.add(new LineMarkerInfo<PsiElement>(
+ element, element.getTextRange(), AllIcons.Actions.Execute, Pass.UPDATE_OVERRIDEN_MARKERS,
+ new Function<PsiElement, String>() {
+ @Override
+ public String fun(PsiElement e) {
+ return "Execute '" + e.getContainingFile().getName() + "'";
+ }
+ },
+ new GutterIconNavigationHandler<PsiElement>() {
+ @Override
+ public void navigate(MouseEvent e, PsiElement elt) {
+ executeCurrentScript(elt);
+ }
+ },
+ GutterIconRenderer.Alignment.RIGHT));
+ }
+ }
+ }
+
+ private static void executeCurrentScript(PsiElement elt) {
+ Editor editor = PsiUtilBase.findEditor(elt);
+ assert editor != null;
+
+ final ConfigurationContext context =
+ ConfigurationContext.getFromContext(DataManager.getInstance().getDataContext(editor.getComponent()));
+ PyRunCurrentFileAction.run(context);
+ }
+
+ private static boolean isFirstCodeLine(PsiElement element) {
+ return element instanceof PyStatement &&
+ element.getParent() instanceof PyFile &&
+ !isNothing(element) &&
+ nothingBefore(element);
+ }
+
+ private static boolean nothingBefore(PsiElement element) {
+ element = element.getPrevSibling();
+ while (element != null) {
+ if (!isNothing(element)) {
+ return false;
+ }
+ element = element.getPrevSibling();
+ }
+
+ return true;
+ }
+
+ private static boolean isNothing(PsiElement element) {
+ return (element instanceof PsiComment) || (element instanceof PyImportStatement) || (element instanceof PsiWhiteSpace);
+ }
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java b/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java
new file mode 100644
index 000000000000..4d30fa29a3b3
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java
@@ -0,0 +1,56 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.execution.Location;
+import com.intellij.execution.RunManagerEx;
+import com.intellij.execution.RunnerAndConfigurationSettings;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.executors.DefaultRunExecutor;
+import com.intellij.execution.runners.ExecutionUtil;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.jetbrains.python.PythonFileType;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author traff
+ */
+public class PyRunCurrentFileAction extends AnAction {
+ public PyRunCurrentFileAction() {
+ getTemplatePresentation().setIcon(AllIcons.Actions.Execute);
+ }
+
+ @Override
+ public void update(AnActionEvent e) {
+ Presentation presentation = e.getPresentation();
+ final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
+ Location location = context.getLocation();
+ if (location != null && location.getPsiElement().getContainingFile() != null && location.getPsiElement().getContainingFile().getFileType() == PythonFileType.INSTANCE) {
+ presentation.setEnabled(true);
+ presentation.setText("Run '" + location.getPsiElement().getContainingFile().getName() + "'");
+ }
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
+
+ run(context);
+ }
+
+ public static void run(@NotNull ConfigurationContext context) {
+ RunnerAndConfigurationSettings configuration = context.findExisting();
+ final RunManagerEx runManager = (RunManagerEx)context.getRunManager();
+ if (configuration == null) {
+ configuration = context.getConfiguration();
+ if (configuration == null) {
+ return;
+ }
+ runManager.setTemporaryConfiguration(configuration);
+ }
+ runManager.setSelectedConfiguration(configuration);
+
+ ExecutionUtil.runConfiguration(configuration, DefaultRunExecutor.getRunExecutorInstance());
+ }
+}