disable line numbers and folding for editing mode (EDU-673, EDU-674)
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / ui / StudyToolWindow.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.edu.learning.ui;
17
18 import com.intellij.ide.browsers.WebBrowserManager;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.actionSystem.*;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.editor.EditorFactory;
24 import com.intellij.openapi.editor.EditorSettings;
25 import com.intellij.openapi.editor.colors.EditorColorsManager;
26 import com.intellij.openapi.editor.ex.EditorEx;
27 import com.intellij.openapi.extensions.Extensions;
28 import com.intellij.openapi.fileEditor.FileDocumentManager;
29 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.SimpleToolWindowPanel;
32 import com.intellij.openapi.util.Disposer;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.ui.JBCardLayout;
35 import com.intellij.ui.JBColor;
36 import com.intellij.ui.OnePixelSplitter;
37 import com.intellij.util.ui.JBUI;
38 import com.jetbrains.edu.learning.*;
39 import com.jetbrains.edu.learning.core.EduNames;
40 import com.jetbrains.edu.learning.courseFormat.*;
41 import com.jetbrains.edu.learning.stepic.StepicAdaptiveReactionsPanel;
42 import org.jetbrains.annotations.NotNull;
43
44 import javax.swing.*;
45 import javax.swing.border.EmptyBorder;
46 import java.awt.*;
47 import java.util.List;
48 import java.util.Map;
49
50 public abstract class StudyToolWindow extends SimpleToolWindowPanel implements DataProvider, Disposable {
51   private static final Logger LOG = Logger.getInstance(StudyToolWindow.class);
52   private static final String TASK_INFO_ID = "taskInfo";
53   private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description";
54
55   private final JBCardLayout myCardLayout;
56   private final JPanel myContentPanel;
57   private final OnePixelSplitter mySplitPane;
58   private JLabel myStatisticLabel;
59   private StudyProgressBar myStudyProgressBar;
60
61   public StudyToolWindow() {
62     super(true, true);
63     myCardLayout = new JBCardLayout();
64     myContentPanel = new JPanel(myCardLayout);
65     mySplitPane = new OnePixelSplitter(myVertical = true);
66   }
67
68   public void init(@NotNull final Project project, final boolean isToolwindow) {
69     String taskText = StudyUtils.getTaskText(project);
70     if (taskText == null) return;
71
72     final DefaultActionGroup group = getActionGroup(project);
73     setActionToolbar(group);
74
75     final JPanel panel = new JPanel(new BorderLayout());
76     final Course course = StudyTaskManager.getInstance(project).getCourse();
77     if (isToolwindow && course != null && course.isAdaptive()) {
78       panel.add(new StepicAdaptiveReactionsPanel(project), BorderLayout.NORTH);
79     }
80     
81     JComponent taskInfoPanel = createTaskInfoPanel(project);
82     panel.add(taskInfoPanel, BorderLayout.CENTER);
83     
84     final JPanel courseProgress = createCourseProgress(project);
85     if (isToolwindow && course != null && !course.isAdaptive() && EduNames.STUDY.equals(course.getCourseMode())) {
86       panel.add(courseProgress, BorderLayout.SOUTH);
87     }
88
89     myContentPanel.add(TASK_INFO_ID, panel);
90     mySplitPane.setFirstComponent(myContentPanel);
91     addAdditionalPanels(project);
92     myCardLayout.show(myContentPanel, TASK_INFO_ID);
93
94     setContent(mySplitPane);
95     
96     if (isToolwindow) {
97       StudyPluginConfigurator configurator = StudyUtils.getConfigurator(project);
98       if (configurator != null) {
99         final FileEditorManagerListener listener = configurator.getFileEditorManagerListener(project, this);
100         project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener);
101       }
102
103       if (StudyTaskManager.getInstance(project).isTurnEditingMode() ||
104           StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
105         TaskFile file = StudyUtils.getSelectedTaskFile(project);
106         if (file != null) {
107           VirtualFile taskDir = file.getTask().getTaskDir(project);
108           setTaskText(taskText, taskDir, project);
109         }
110       }
111       else {
112         setTaskText(taskText, null, project);
113       }
114     }
115   }
116   
117   public void setTopComponent(@NotNull final JComponent component) {
118     mySplitPane.setFirstComponent(component);
119   }
120   
121   public void setDefaultTopComponent() {
122     mySplitPane.setFirstComponent(myContentPanel);
123   }
124
125   public void setActionToolbar(DefaultActionGroup group) {
126     JPanel toolbarPanel = createToolbarPanel(group);
127     setToolbar(toolbarPanel);
128   }
129
130   private void addAdditionalPanels(Project project) {
131     StudyPluginConfigurator configurator = StudyUtils.getConfigurator(project);
132     if (configurator != null) {
133       Map<String, JPanel> panels = configurator.getAdditionalPanels(project);
134       for (Map.Entry<String, JPanel> entry : panels.entrySet()) {
135         myContentPanel.add(entry.getKey(), entry.getValue());
136       }
137     }
138   }
139
140
141   public void dispose() {
142   }
143
144   //used in checkiO plugin.
145   @SuppressWarnings("unused")
146   public void showPanelById(@NotNull final String panelId) {
147     myCardLayout.swipe(myContentPanel, panelId, JBCardLayout.SwipeDirection.AUTO);
148   }
149
150   //used in checkiO plugin.
151   @SuppressWarnings("unused")
152   public void setBottomComponent(JComponent component) {
153     mySplitPane.setSecondComponent(component);
154   }
155
156   //used in checkiO plugin.
157   @SuppressWarnings("unused")
158   public JComponent getBottomComponent() {
159     return mySplitPane.getSecondComponent();
160   }
161
162   //used in checkiO plugin.
163   @SuppressWarnings("unused")
164   public void setTopComponentPrefferedSize(@NotNull final Dimension dimension) {
165     myContentPanel.setPreferredSize(dimension);
166   }
167
168   //used in checkiO plugin.
169   @SuppressWarnings("unused")
170   public JPanel getContentPanel() {
171     return myContentPanel;
172   }
173
174
175   public abstract JComponent createTaskInfoPanel(Project project);
176
177   public static JPanel createToolbarPanel(ActionGroup group) {
178     final ActionToolbar actionToolBar = ActionManager.getInstance().createActionToolbar("Study", group, true);
179     return JBUI.Panels.simplePanel(actionToolBar.getComponent());
180   }
181
182   public static DefaultActionGroup getActionGroup(@NotNull final Project project) {
183     DefaultActionGroup group = new DefaultActionGroup();
184     Course course = StudyTaskManager.getInstance(project).getCourse();
185     if (course == null) {
186       LOG.warn("Course is null");
187       return group;
188     }
189     StudyPluginConfigurator configurator = StudyUtils.getConfigurator(project);
190     if (configurator != null) {
191       group.addAll(configurator.getActionGroup(project));
192       addAdditionalActions(group);
193       return group;
194     }
195     else {
196       LOG.warn("No configurator is provided for plugin");
197       return StudyBasePluginConfigurator.getDefaultActionGroup();
198     }
199   }
200
201   private static void addAdditionalActions(DefaultActionGroup group) {
202     StudyActionsProvider[] providers = Extensions.getExtensions(StudyActionsProvider.EP_NAME);
203     for (StudyActionsProvider provider : providers) {
204       group.addAll(provider.getActions());
205     }
206   }
207
208   public void setTaskText(String text, VirtualFile taskDirectory, Project project) {
209     if (!EMPTY_TASK_TEXT.equals(text) && StudyTaskManager.getInstance(project).isTurnEditingMode()) {
210       if (taskDirectory == null) {
211         LOG.info("Failed to enter editing mode for StudyToolWindow");
212         return;
213       }
214       VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(project, taskDirectory);
215       enterEditingMode(taskTextFile, project);
216       StudyTaskManager.getInstance(project).setTurnEditingMode(false);
217     }
218     if (taskDirectory != null && StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
219       VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(project, taskDirectory);
220       enterEditingMode(taskTextFile, project);
221     }
222     else {
223       setText(text);
224     }
225   }
226
227   protected abstract void setText(@NotNull String text);
228
229   public void setEmptyText(@NotNull Project project) {
230     if (StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) {
231       mySplitPane.setFirstComponent(myContentPanel);
232       StudyTaskManager.getInstance(project).setTurnEditingMode(true);
233     }
234     setTaskText(EMPTY_TASK_TEXT, null, project);
235   }
236
237   public enum StudyToolWindowMode {
238     TEXT, EDITING
239   }
240
241
242   public void enterEditingMode(VirtualFile taskFile, Project project) {
243     final EditorFactory factory = EditorFactory.getInstance();
244     Document document = FileDocumentManager.getInstance().getDocument(taskFile);
245     if (document == null) {
246       return;
247     }
248     WebBrowserManager.getInstance().setShowBrowserHover(false);
249     final EditorEx createdEditor = (EditorEx)factory.createEditor(document, project, taskFile, false);
250     Disposer.register(project, new Disposable() {
251       public void dispose() {
252         factory.releaseEditor(createdEditor);
253       }
254     });
255     JComponent editorComponent = createdEditor.getComponent();
256     editorComponent.setBorder(new EmptyBorder(10, 20, 0, 10));
257     editorComponent.setBackground(EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground());
258     EditorSettings editorSettings = createdEditor.getSettings();
259     editorSettings.setLineMarkerAreaShown(false);
260     editorSettings.setLineNumbersShown(false);
261     editorSettings.setFoldingOutlineShown(false);
262     mySplitPane.setFirstComponent(editorComponent);
263     mySplitPane.repaint();
264
265     StudyTaskManager.getInstance(project).setToolWindowMode(StudyToolWindowMode.EDITING);
266   }
267
268
269   public void leaveEditingMode(Project project) {
270     WebBrowserManager.getInstance().setShowBrowserHover(true);
271     mySplitPane.setFirstComponent(myContentPanel);
272     StudyTaskManager.getInstance(project).setToolWindowMode(StudyToolWindowMode.TEXT);
273     StudyUtils.updateToolWindows(project);
274   }
275
276   private JPanel createCourseProgress(@NotNull final Project project) {
277     JPanel contentPanel = new JPanel();
278     contentPanel.setBackground(JBColor.WHITE);
279     contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.PAGE_AXIS));
280     contentPanel.add(Box.createRigidArea(new Dimension(10, 0)));
281     contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
282     myStudyProgressBar = new StudyProgressBar(0, 20, 10);
283
284     myStatisticLabel = new JLabel("", SwingConstants.LEFT);
285     contentPanel.add(myStatisticLabel);
286     contentPanel.add(myStudyProgressBar);
287
288     contentPanel.setPreferredSize(new Dimension(100, 60));
289     contentPanel.setMinimumSize(new Dimension(300, 40));
290     updateCourseProgress(project);
291     return contentPanel;
292   }
293
294   public void updateCourseProgress(@NotNull final Project project) {
295     final Course course = StudyTaskManager.getInstance(project).getCourse();
296     if (course != null) {
297       int taskNum = 0;
298       int taskSolved = 0;
299       List<Lesson> lessons = course.getLessons();
300       for (Lesson lesson : lessons) {
301         if (lesson.getName().equals(EduNames.PYCHARM_ADDITIONAL)) continue;
302         taskNum += lesson.getTaskList().size();
303         taskSolved += getSolvedTasks(lesson);
304       }
305       String completedTasks = String.format("%d of %d tasks completed", taskSolved, taskNum);
306       double percent = (taskSolved * 100.0) / taskNum;
307
308       myStatisticLabel.setText(completedTasks);
309       myStudyProgressBar.setFraction(percent / 100);
310     }
311   }
312
313   private static int getSolvedTasks(@NotNull final Lesson lesson) {
314     int solved = 0;
315     for (Task task : lesson.getTaskList()) {
316       if (task.getStatus() == StudyStatus.Solved) {
317         solved += 1;
318       }
319     }
320     return solved;
321   }
322 }