diff options
Diffstat (limited to 'python/edu/learn-python/src/com/jetbrains/python/edu/ui')
6 files changed, 545 insertions, 0 deletions
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyCondition.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyCondition.java new file mode 100644 index 000000000000..5add6c934ecd --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyCondition.java @@ -0,0 +1,25 @@ +package com.jetbrains.python.edu.ui; + +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.jetbrains.python.edu.StudyTaskManager; + +/** + * author: liana + * data: 7/29/14. + */ +public class StudyCondition implements Condition, DumbAware { + public static boolean VALUE = false; + @Override + public boolean value(Object o) { + if (o instanceof Project) { + Project project = (Project) o; + StudyTaskManager taskManager = StudyTaskManager.getInstance(project); + if (taskManager.getCourse() != null) { + VALUE = true; + } + } + return false; + } +} 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 new file mode 100644 index 000000000000..133c38d4e8f8 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.edu.ui.StudyNewProjectPanel"> + <grid id="27dc6" binding="myContentPanel" layout-manager="GridLayoutManager" row-count="2" 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> + <grid id="54488" layout-manager="GridLayoutManager" row-count="2" 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="1" column="1" row-span="1" col-span="3" vsize-policy="2" hsize-policy="4" anchor="0" fill="3" indent="0" use-parent-layout="false"> + <minimum-size width="-1" height="60"/> + <preferred-size width="-1" height="60"/> + </grid> + </constraints> + <properties/> + <border type="line"> + <color color="-6709600"/> + </border> + <children> + <component id="213f6" class="javax.swing.JLabel" binding="myAuthorLabel"> + <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="d754d" class="javax.swing.JLabel" binding="myDescriptionLabel"> + <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"/> + </constraints> + <properties> + <text value=""/> + </properties> + </component> + </children> + </grid> + <component id="6c40c" class="javax.swing.JLabel"> + <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> + </constraints> + <properties> + <font/> + <horizontalTextPosition value="0"/> + <text value="Courses:"/> + </properties> + </component> + <component id="21ac6" class="javax.swing.JComboBox" binding="myCoursesComboBox"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + </component> + <component id="5c614" class="com.intellij.openapi.ui.FixedSizeButton" binding="myBrowseButton"> + <constraints> + <grid row="0" column="2" 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="25"/> + </grid> + </constraints> + <properties/> + </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> + </constraints> + <properties> + <hideActionText value="false"/> + <text value=""/> + <toolTipText value="Refresh course list"/> + <verticalAlignment value="1"/> + <verticalTextPosition value="1"/> + </properties> + </component> + </children> + </grid> +</form> 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 new file mode 100644 index 000000000000..0f1ec08a8856 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java @@ -0,0 +1,196 @@ +package com.jetbrains.python.edu.ui; + +import com.intellij.facet.ui.FacetValidatorsManager; +import com.intellij.facet.ui.ValidationResult; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.vfs.VirtualFile; +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; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * author: liana + * data: 7/31/14. + */ +public class StudyNewProjectPanel{ + private Set<CourseInfo> myAvailableCourses = new HashSet<CourseInfo>(); + private JComboBox myCoursesComboBox; + private JButton myBrowseButton; + private JButton myRefreshButton; + private JPanel myContentPanel; + private JLabel myAuthorLabel; + private JLabel myDescriptionLabel; + 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"; + private FacetValidatorsManager myValidationManager; + + public StudyNewProjectPanel(StudyDirectoryProjectGenerator generator) { + myGenerator = generator; + Map<CourseInfo, File> courses = myGenerator.getCourses(); + if (courses.isEmpty()) { + setError(CONNECTION_ERROR); + } + else { + myAvailableCourses = courses.keySet(); + for (CourseInfo courseInfo : myAvailableCourses) { + myCoursesComboBox.addItem(courseInfo); + } + myAuthorLabel.setText("Author: " + StudyUtils.getFirst(myAvailableCourses).getAuthor()); + myDescriptionLabel.setText(StudyUtils.getFirst(myAvailableCourses).getDescription()); + //setting the first course in list as selected + myGenerator.setSelectedCourse(StudyUtils.getFirst(myAvailableCourses)); + setOK(); + } + initListeners(); + myRefreshButton.setVisible(true); + myRefreshButton.setIcon(StudyIcons.Refresh); + } + + private void initListeners() { + + final FileChooserDescriptor fileChooser = new FileChooserDescriptor(true, false, false, true, false, false) { + @Override + public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { + return file.isDirectory() || StudyUtils.isZip(file.getName()); + } + + @Override + public boolean isFileSelectable(VirtualFile file) { + return StudyUtils.isZip(file.getName()); + } + }; + myBrowseButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + FileChooser.chooseFile(fileChooser, null, null, + new Consumer<VirtualFile>() { + @Override + public void consume(VirtualFile file) { + String fileName = file.getPath(); + int oldSize = myAvailableCourses.size(); + CourseInfo courseInfo = myGenerator.addLocalCourse(fileName); + if (courseInfo != null) { + if (oldSize != myAvailableCourses.size()) { + myCoursesComboBox.addItem(courseInfo); + } + myCoursesComboBox.setSelectedItem(courseInfo); + setOK(); + } + else { + setError(INVALID_COURSE); + myCoursesComboBox.removeAllItems(); + myCoursesComboBox.addItem(CourseInfo.INVALID_COURSE); + for (CourseInfo course : myAvailableCourses) { + myCoursesComboBox.addItem(course); + } + myCoursesComboBox.setSelectedItem(CourseInfo.INVALID_COURSE); + } + } + }); + } + }); + myRefreshButton.addActionListener(new RefreshActionListener()); + myCoursesComboBox.addActionListener(new CourseSelectedListener()); + } + + private void setError(String errorMessage) { + myGenerator.setValidationResult(new ValidationResult(errorMessage)); + if (myValidationManager != null) { + myValidationManager.validate(); + } + } + + private void setOK() { + myGenerator.setValidationResult(ValidationResult.OK); + if (myValidationManager != null) { + myValidationManager.validate(); + } + } + + public JPanel getContentPanel() { + return myContentPanel; + } + + public void registerValidators(final FacetValidatorsManager manager) { + myValidationManager = manager; + } + + + /** + * Handles refreshing courses + * Old courses added to new courses only if their + * meta file still exists in local file system + */ + private class RefreshActionListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + myGenerator.downloadAndUnzip(true); + Map<CourseInfo, File> downloadedCourses = myGenerator.loadCourses(); + if (downloadedCourses.isEmpty()) { + setError(CONNECTION_ERROR); + return; + } + Map<CourseInfo, File> oldCourses = myGenerator.getLoadedCourses(); + Map<CourseInfo, File> newCourses = new HashMap<CourseInfo, File>(); + for (Map.Entry<CourseInfo, File> course : oldCourses.entrySet()) { + File courseFile = course.getValue(); + if (courseFile.exists()) { + newCourses.put(course.getKey(), courseFile); + } + } + for (Map.Entry<CourseInfo, File> course : downloadedCourses.entrySet()) { + CourseInfo courseName = course.getKey(); + if (newCourses.get(courseName) == null) { + newCourses.put(courseName, course.getValue()); + } + } + myCoursesComboBox.removeAllItems(); + + for (CourseInfo courseInfo : newCourses.keySet()) { + myCoursesComboBox.addItem(courseInfo); + } + myGenerator.setSelectedCourse(StudyUtils.getFirst(newCourses.keySet())); + + myGenerator.setCourses(newCourses); + myAvailableCourses = newCourses.keySet(); + myGenerator.flushCache(); + } + } + + + /** + * Handles selecting course in combo box + * Sets selected course in combo box as selected in + * {@link StudyNewProjectPanel#myGenerator} + */ + private class CourseSelectedListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + JComboBox cb = (JComboBox)e.getSource(); + CourseInfo selectedCourse = (CourseInfo)cb.getSelectedItem(); + if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) { + myAuthorLabel.setText(""); + myDescriptionLabel.setText(""); + return; + } + myAuthorLabel.setText("Author: " + selectedCourse.getAuthor()); + myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE); + myDescriptionLabel.setText(selectedCourse.getDescription()); + myGenerator.setSelectedCourse(selectedCourse); + setOK(); + } + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyProgressBar.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyProgressBar.java new file mode 100644 index 000000000000..97fa00d0f70d --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyProgressBar.java @@ -0,0 +1,92 @@ +package com.jetbrains.python.edu.ui; + +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.ui.GraphicsConfig; +import com.intellij.ui.ColorUtil; +import com.intellij.ui.Gray; +import com.intellij.ui.JBColor; +import com.intellij.util.ui.GraphicsUtil; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.Rectangle2D; + +public class StudyProgressBar extends JComponent implements DumbAware { + public static final Color BLUE = JBColor.BLUE; + private static final Color SHADOW1 = new JBColor(Gray._190, JBColor.border()); + private static final Color SHADOW2 = Gray._105; + private static final int BRICK_WIDTH = 10; + private static final int BRICK_SPACE = 2; + private final int myHeight; + private final int myIndent; + private double myFraction = 0.0; + private Color myColor = BLUE; + + public StudyProgressBar(double fraction, Color color, int height, int indent) { + myFraction = fraction; + myColor = color; + myHeight = height; + myIndent = indent; + } + + private int getBricksToDraw(double fraction) { + int bricksTotal = (getWidth() - 8) / (BRICK_WIDTH + BRICK_SPACE); + return (int)(bricksTotal * fraction) + 1; + } + + protected void paintComponent(Graphics g) { + final GraphicsConfig config = GraphicsUtil.setupAAPainting(g); + Graphics2D g2 = (Graphics2D)g; + if (myFraction > 1) { + myFraction = 1; + } + + Dimension size = getSize(); + double width = size.getWidth() - 2*myIndent; + g2.setPaint(UIUtil.getListBackground()); + Rectangle2D rect = new Rectangle2D.Double(myIndent, 0, width, myHeight); + g2.fill(rect); + + g2.setPaint(new JBColor(SHADOW1, JBColor.border())); + rect.setRect(myIndent, 0, width, myHeight); + int arcWidth = 5; + int arcHeight = 5; + g2.drawRoundRect(myIndent, 0, (int)width, myHeight, arcWidth, arcHeight); + g2.setPaint(SHADOW2); + g2.drawRoundRect(myIndent, 0, (int)width, myHeight, arcWidth, arcHeight); + + int y_center = myHeight / 2; + int y_steps = myHeight / 2 - 3; + int alpha_step = y_steps > 0 ? (255 - 70) / y_steps : 255 - 70; + int x_offset = 4; + + g.setClip(4 + myIndent, 3, (int)width - 6, myHeight - 4); + + int bricksToDraw = myFraction == 0 ? 0 : getBricksToDraw(myFraction); + for (int i = 0; i < bricksToDraw; i++) { + g2.setPaint(myColor); + UIUtil.drawLine(g2, x_offset, y_center, x_offset + BRICK_WIDTH - 1, y_center); + for (int j = 0; j < y_steps; j++) { + Color color = ColorUtil.toAlpha(myColor, 255 - alpha_step * (j + 1)); + g2.setPaint(color); + UIUtil.drawLine(g2, x_offset, y_center - 1 - j, x_offset + BRICK_WIDTH - 1, y_center - 1 - j); + if (!(y_center % 2 != 0 && j == y_steps - 1)) { + UIUtil.drawLine(g2, x_offset, y_center + 1 + j, x_offset + BRICK_WIDTH - 1, y_center + 1 + j); + } + } + g2.setColor( + ColorUtil.toAlpha(myColor, 255 - alpha_step * (y_steps / 2 + 1))); + g2.drawRect(x_offset, y_center - y_steps, BRICK_WIDTH - 1, myHeight - 7); + x_offset += BRICK_WIDTH + BRICK_SPACE; + } + config.restore(); + } + + @Override + public Dimension getMaximumSize() { + Dimension dimension = super.getMaximumSize(); + dimension.height = myHeight + 10; + return dimension; + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyTestContentPanel.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyTestContentPanel.java new file mode 100644 index 000000000000..dee4fbaf9d01 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyTestContentPanel.java @@ -0,0 +1,67 @@ +package com.jetbrains.python.edu.ui; + +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.components.JBScrollPane; +import org.jetbrains.annotations.NotNull; +import com.jetbrains.python.edu.course.UserTest; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.text.BadLocationException; +import java.awt.*; + +public class StudyTestContentPanel extends JPanel { + public static final Dimension PREFERRED_SIZE = new Dimension(300, 200); + private static final Font HEADER_FONT = new Font("Arial", Font.BOLD, 16); + private final JTextArea myInputArea = new JTextArea(); + private final JTextArea myOutputArea = new JTextArea(); + public StudyTestContentPanel(UserTest userTest) { + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + initContentLabel("input", myInputArea); + myInputArea.getDocument().addDocumentListener(new BufferUpdater(userTest.getInputBuffer())); + myOutputArea.getDocument().addDocumentListener(new BufferUpdater(userTest.getOutputBuffer())); + initContentLabel("output", myOutputArea); + setEditable(userTest.isEditable()); + } + + private void initContentLabel(final String headerText, @NotNull final JTextArea contentArea) { + JLabel headerLabel = new JLabel(headerText); + headerLabel.setFont(HEADER_FONT); + this.add(headerLabel); + this.add(new JSeparator(SwingConstants.HORIZONTAL)); + JScrollPane scroll = new JBScrollPane(contentArea); + scroll.setPreferredSize(PREFERRED_SIZE); + this.add(scroll); + } + + private void setEditable(boolean isEditable) { + myInputArea.setEditable(isEditable); + myOutputArea.setEditable(isEditable); + } + public void addInputContent(final String content) { + myInputArea.setText(content); + } + + public void addOutputContent(final String content) { + myOutputArea.setText(content); + } + + private class BufferUpdater extends DocumentAdapter { + private final StringBuilder myBuffer; + + private BufferUpdater(StringBuilder buffer) { + myBuffer = buffer; + } + + @Override + protected void textChanged(DocumentEvent e) { + myBuffer.delete(0, myBuffer.length()); + try { + myBuffer.append(e.getDocument().getText(0, e.getDocument().getLength())); + } + catch (BadLocationException e1) { + e1.printStackTrace(); + } + } + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java new file mode 100644 index 000000000000..a553978c416a --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java @@ -0,0 +1,81 @@ +package com.jetbrains.python.edu.ui; + +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowFactory; +import com.intellij.ui.JBColor; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentFactory; +import com.intellij.util.ui.UIUtil; +import com.jetbrains.python.edu.StudyTaskManager; +import com.jetbrains.python.edu.course.Course; +import com.jetbrains.python.edu.course.Lesson; +import com.jetbrains.python.edu.course.LessonInfo; +import com.jetbrains.python.edu.course.StudyStatus; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware { + public static final String STUDY_TOOL_WINDOW = "Course Description"; + JPanel contentPanel = new JPanel(); + + @Override + public void createToolWindowContent(@NotNull final Project project, @NotNull final ToolWindow toolWindow) { + if (StudyTaskManager.getInstance(project).getCourse() != null) { + contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.PAGE_AXIS)); + contentPanel.add(Box.createRigidArea(new Dimension(10, 0))); + StudyTaskManager taskManager = StudyTaskManager.getInstance(project); + Course course = taskManager.getCourse(); + if (course == null) { + return; + } + String courseName = UIUtil.toHtml("<h1>" + course.getName() + "</h1>", 10); + String description = UIUtil.toHtml(course.getDescription(), 5); + String author = taskManager.getCourse().getAuthor(); + String authorLabel = UIUtil.toHtml("<b>Author: </b>" + author, 5); + contentPanel.add(new JLabel(courseName)); + contentPanel.add(new JLabel(authorLabel)); + contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); + contentPanel.add(new JLabel(description)); + + int taskNum = 0; + int taskSolved = 0; + int lessonsCompleted = 0; + List<Lesson> lessons = course.getLessons(); + for (Lesson lesson : lessons) { + if (lesson.getStatus() == StudyStatus.Solved) { + lessonsCompleted++; + } + LessonInfo lessonInfo = lesson.getLessonInfo(); + taskNum += lessonInfo.getTaskNum(); + taskSolved += lessonInfo.getTaskSolved(); + } + String completedLessons = String.format("%d of %d lessons completed", lessonsCompleted, course.getLessons().size()); + String completedTasks = String.format("%d of %d tasks completed", taskSolved, taskNum); + String tasksLeft = String.format("%d of %d tasks left", taskNum - taskSolved, taskNum); + contentPanel.add(Box.createVerticalStrut(10)); + addStatistics(completedLessons); + addStatistics(completedTasks); + + double percent = (taskSolved * 100.0) / taskNum; + contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); + StudyProgressBar studyProgressBar = new StudyProgressBar(percent / 100, JBColor.GREEN, 40, 10); + contentPanel.add(studyProgressBar); + addStatistics(tasksLeft); + ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); + Content content = contentFactory.createContent(contentPanel, "", true); + toolWindow.getContentManager().addContent(content); + } + } + + private void addStatistics(String statistics) { + String labelText = UIUtil.toHtml(statistics, 5); + contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); + JLabel statisticLabel = new JLabel(labelText); + contentPanel.add(statisticLabel); + } +} |