89ef2ae07938614451ea8bb3f3e2c2d4dc29c971
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / ui / StudyNewProjectPanel.java
1 package com.jetbrains.edu.learning.ui;
2
3 import com.intellij.facet.ui.FacetValidatorsManager;
4 import com.intellij.facet.ui.ValidationResult;
5 import com.intellij.icons.AllIcons;
6 import com.intellij.openapi.application.ApplicationManager;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.fileChooser.FileChooser;
9 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
10 import com.intellij.openapi.progress.ProgressManager;
11 import com.intellij.openapi.project.DefaultProjectFactoryImpl;
12 import com.intellij.openapi.project.Project;
13 import com.intellij.openapi.project.ProjectManager;
14 import com.intellij.openapi.ui.DialogWrapper;
15 import com.intellij.openapi.ui.popup.JBPopupFactory;
16 import com.intellij.openapi.ui.popup.ListPopup;
17 import com.intellij.openapi.ui.popup.PopupStep;
18 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
19 import com.intellij.openapi.util.text.StringUtil;
20 import com.intellij.openapi.vfs.VirtualFile;
21 import com.intellij.util.Consumer;
22 import com.jetbrains.edu.learning.courseFormat.Course;
23 import com.jetbrains.edu.learning.StudyUtils;
24 import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
25 import com.jetbrains.edu.learning.stepic.CourseInfo;
26 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
27 import com.jetbrains.edu.learning.stepic.StepicUser;
28 import icons.InteractiveLearningIcons;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import javax.swing.*;
33 import java.awt.*;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.concurrent.Callable;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.Future;
41 import java.util.concurrent.TimeUnit;
42
43 /**
44  * author: liana
45  * data: 7/31/14.
46  */
47 public class StudyNewProjectPanel {
48   private static final Logger LOG = Logger.getInstance(StudyNewProjectPanel.class);
49   private List<CourseInfo> myAvailableCourses = new ArrayList<CourseInfo>();
50   private JButton myBrowseButton;
51   private JComboBox<CourseInfo> myCoursesComboBox;
52   private JButton myRefreshButton;
53   private JPanel myContentPanel;
54   private JLabel myAuthorLabel;
55   private JLabel myLabel;
56   private JPanel myInfoPanel;
57   private JTextPane myDescriptionLabel;
58   private final StudyProjectGenerator myGenerator;
59   private static final String CONNECTION_ERROR = "<html>Failed to download courses.<br>Check your Internet connection.</html>";
60   private static final String INVALID_COURSE = "Selected course is invalid";
61   private FacetValidatorsManager myValidationManager;
62
63   public StudyNewProjectPanel(StudyProjectGenerator generator) {
64     myGenerator = generator;
65     myAvailableCourses = myGenerator.getCourses(false);
66     myBrowseButton.setPreferredSize(new Dimension(28, 28));
67     myRefreshButton.setPreferredSize(new Dimension(28, 28));
68     if (myAvailableCourses.isEmpty()) {
69       setError(CONNECTION_ERROR);
70     }
71     else {
72       for (CourseInfo courseInfo : myAvailableCourses) {
73         myCoursesComboBox.addItem(courseInfo);
74       }
75       final CourseInfo selectedCourse = StudyUtils.getFirst(myAvailableCourses);
76       final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
77       myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
78       myDescriptionLabel.setText(selectedCourse.getDescription());
79       myDescriptionLabel.setEditable(false);
80       //setting the first course in list as selected
81       myGenerator.setSelectedCourse(selectedCourse);
82       setOK();
83     }
84     initListeners();
85     myRefreshButton.setVisible(true);
86     myRefreshButton.putClientProperty("JButton.buttonType", "null");
87     myRefreshButton.setIcon(AllIcons.Actions.Refresh);
88
89     myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize());
90   }
91
92   private void setupBrowseButton() {
93     myBrowseButton.putClientProperty("JButton.buttonType", "null");
94     myBrowseButton.setIcon(InteractiveLearningIcons.InterpreterGear);
95     final FileChooserDescriptor fileChooser = new FileChooserDescriptor(true, false, false, true, false, false) {
96       @Override
97       public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
98         return file.isDirectory() || StudyUtils.isZip(file.getName());
99       }
100
101       @Override
102       public boolean isFileSelectable(VirtualFile file) {
103         return StudyUtils.isZip(file.getName());
104       }
105     };
106     myBrowseButton.addActionListener(new ActionListener() {
107       public void actionPerformed(ActionEvent e) {
108         final BaseListPopupStep<String> popupStep = new BaseListPopupStep<String>("", "Add local course", "Load private courses") {
109           @Override
110           public PopupStep onChosen(final String selectedValue, boolean finalChoice) {
111             return doFinalStep(new Runnable() {
112               public void run() {
113                 if ("Add local course".equals(selectedValue)) {
114
115                   Project[] projects = ProjectManager.getInstance().getOpenProjects();
116                   FileChooser.chooseFile(fileChooser, null, projects.length == 0 ? null : projects[0].getBaseDir(),
117                                          new Consumer<VirtualFile>() {
118                                            @Override
119                                            public void consume(VirtualFile file) {
120                                              String fileName = file.getPath();
121                                              int oldSize = myAvailableCourses.size();
122                                              CourseInfo courseInfo = myGenerator.addLocalCourse(fileName);
123                                              if (courseInfo != null) {
124                                                if (oldSize != myAvailableCourses.size()) {
125                                                  myCoursesComboBox.addItem(courseInfo);
126                                                }
127                                                myCoursesComboBox.setSelectedItem(courseInfo);
128                                                setOK();
129                                              }
130                                              else {
131                                                setError(INVALID_COURSE);
132                                                myCoursesComboBox.removeAllItems();
133                                                myCoursesComboBox.addItem(CourseInfo.INVALID_COURSE);
134                                                for (CourseInfo course : myAvailableCourses) {
135                                                  myCoursesComboBox.addItem(course);
136                                                }
137                                                myCoursesComboBox.setSelectedItem(CourseInfo.INVALID_COURSE);
138                                              }
139                                            }
140                                          });
141                 }
142                 else if ("Load private courses".equals(selectedValue)) {
143                   final AddRemoteDialog dialog = new AddRemoteDialog();
144                   dialog.show();
145                 }
146               }
147             });
148           }
149         };
150         final ListPopup popup = JBPopupFactory.getInstance().createListPopup(popupStep);
151         popup.showInScreenCoordinates(myBrowseButton, myBrowseButton.getLocationOnScreen());
152       }
153     });
154   }
155
156   private void initListeners() {
157     myRefreshButton.addActionListener(new RefreshActionListener());
158     myCoursesComboBox.addActionListener(new CourseSelectedListener());
159     setupBrowseButton();
160   }
161
162   private void setError(@NotNull final String errorMessage) {
163     myGenerator.fireStateChanged(new ValidationResult(errorMessage));
164     if (myValidationManager != null) {
165       myValidationManager.validate();
166     }
167   }
168
169   private void setOK() {
170     myGenerator.fireStateChanged(ValidationResult.OK);
171     if (myValidationManager != null) {
172       myValidationManager.validate();
173     }
174   }
175
176   public JPanel getContentPanel() {
177     return myContentPanel;
178   }
179
180   public void registerValidators(final FacetValidatorsManager manager) {
181     myValidationManager = manager;
182   }
183
184
185   /**
186    * Handles refreshing courses
187    * Old courses added to new courses only if their
188    * meta file still exists in local file system
189    */
190   private class RefreshActionListener implements ActionListener {
191     @Override
192     public void actionPerformed(ActionEvent e) {
193       refreshCoursesList();
194     }
195   }
196
197   private void refreshCoursesList() {
198     final List<CourseInfo> courses = myGenerator.getCourses(true);
199     if (courses.isEmpty()) {
200       setError(CONNECTION_ERROR);
201       return;
202     }
203     myCoursesComboBox.removeAllItems();
204
205     for (CourseInfo courseInfo : courses) {
206       myCoursesComboBox.addItem(courseInfo);
207     }
208     myGenerator.setSelectedCourse(StudyUtils.getFirst(courses));
209
210     myGenerator.setCourses(courses);
211     myAvailableCourses = courses;
212     StudyProjectGenerator.flushCache(myAvailableCourses);
213   }
214
215
216   /**
217    * Handles selecting course in combo box
218    * Sets selected course in combo box as selected in
219    * {@link StudyNewProjectPanel#myGenerator}
220    */
221   private class CourseSelectedListener implements ActionListener {
222     @Override
223     public void actionPerformed(ActionEvent e) {
224       JComboBox cb = (JComboBox)e.getSource();
225       CourseInfo selectedCourse = (CourseInfo)cb.getSelectedItem();
226       if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
227         myAuthorLabel.setText("");
228         myDescriptionLabel.setText("");
229         return;
230       }
231       final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors());
232       myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : "");
233       myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE);
234       myDescriptionLabel.setText(selectedCourse.getDescription());
235       myGenerator.setSelectedCourse(selectedCourse);
236       setOK();
237     }
238   }
239
240   public JComboBox getCoursesComboBox() {
241     return myCoursesComboBox;
242   }
243
244   public JPanel getInfoPanel() {
245     return myInfoPanel;
246   }
247
248   private class AddRemoteDialog extends DialogWrapper {
249
250     private final StudyAddRemoteCourse myRemoteCourse;
251
252     protected AddRemoteDialog() {
253       super(null);
254       myRemoteCourse = new StudyAddRemoteCourse();
255       init();
256     }
257
258     @Nullable
259     @Override
260     protected JComponent createCenterPanel() {
261       return myRemoteCourse.getContentPanel();
262     }
263
264     @Nullable
265     @Override
266     public JComponent getPreferredFocusedComponent() {
267       return myRemoteCourse.getLoginField();
268     }
269
270     @Override
271     protected void doOKAction() {
272       if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getLogin())) {
273         myRemoteCourse.setError("Please, enter your login");
274         return;
275       }
276       if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getPassword())) {
277         myRemoteCourse.setError("Please, enter your password");
278         return;
279       }
280
281       super.doOKAction();
282       final ProgressManager progressManager = ProgressManager.getInstance();
283       progressManager.runProcessWithProgressSynchronously(() -> {
284         final Future<StepicUser> future = ApplicationManager.getApplication().executeOnPooledThread(
285           new Callable<StepicUser>() {
286             @Override
287             public StepicUser call() throws Exception {
288               return EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword());
289             }
290           });
291
292         while (!future.isCancelled() && !future.isDone()) {
293           progressManager.getProgressIndicator().checkCanceled();
294           try {
295             TimeUnit.MILLISECONDS.sleep(500);
296           }
297           catch (final InterruptedException e) {
298             LOG.warn(e.getMessage());
299           }
300         }
301
302         try {
303           final StepicUser stepicUser = future.get();
304           if (stepicUser != null) {
305             stepicUser.setEmail(myRemoteCourse.getLogin());
306             stepicUser.setPassword(myRemoteCourse.getPassword());
307             myGenerator.myUser = stepicUser;
308             ApplicationManager.getApplication().invokeLater(StudyNewProjectPanel.this::refreshCoursesList);
309           }
310           else {
311             setError("Failed to log in");
312           }
313         }
314         catch (InterruptedException e) {
315           LOG.warn(e.getMessage());
316         }
317         catch (ExecutionException e) {
318           LOG.warn(e.getMessage());
319         }
320       }, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject());
321     }
322   }
323 }