Fix PY-14096 Minor Refactor output panel creation
[idea/community.git] / python / ipnb / src / org / jetbrains / plugins / ipnb / editor / panels / code / IpnbCodePanel.java
1 package org.jetbrains.plugins.ipnb.editor.panels.code;
2
3 import com.google.common.collect.Lists;
4 import com.intellij.icons.AllIcons;
5 import com.intellij.openapi.application.Application;
6 import com.intellij.openapi.application.ApplicationManager;
7 import com.intellij.openapi.application.ModalityState;
8 import com.intellij.openapi.editor.Document;
9 import com.intellij.openapi.editor.Editor;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.ui.VerticalFlowLayout;
12 import com.intellij.openapi.util.text.StringUtil;
13 import com.intellij.ui.OnePixelSplitter;
14 import com.intellij.util.ui.UIUtil;
15 import org.jetbrains.annotations.NotNull;
16 import org.jetbrains.annotations.Nullable;
17 import org.jetbrains.plugins.ipnb.configuration.IpnbConnectionManager;
18 import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
19 import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
20 import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel;
21 import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
22 import org.jetbrains.plugins.ipnb.editor.panels.IpnbPanel;
23 import org.jetbrains.plugins.ipnb.format.cells.IpnbCodeCell;
24 import org.jetbrains.plugins.ipnb.format.cells.output.*;
25
26 import javax.swing.*;
27 import java.awt.*;
28 import java.awt.event.MouseAdapter;
29 import java.awt.event.MouseEvent;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.Map;
33
34 public class IpnbCodePanel extends IpnbEditablePanel<JComponent, IpnbCodeCell> {
35   private final Project myProject;
36   @NotNull private final IpnbFileEditor myParent;
37   private IpnbCodeSourcePanel myCodeSourcePanel;
38   private final List<IpnbPanel> myOutputPanels = Lists.newArrayList();
39   private boolean mySelectNext;
40
41   public IpnbCodePanel(@NotNull final Project project, @NotNull final IpnbFileEditor parent, @NotNull final IpnbCodeCell cell) {
42     super(cell, new BorderLayout());
43     myProject = project;
44     myParent = parent;
45
46     myViewPanel = createViewPanel();
47     add(myViewPanel);
48   }
49
50   @NotNull
51   public IpnbFileEditor getFileEditor() {
52     return myParent;
53   }
54
55   public Editor getEditor() {
56     return myCodeSourcePanel.getEditor();
57   }
58
59   public void addPromptPanel(@NotNull final JComponent parent, Integer promptNumber,
60                              @NotNull final IpnbEditorUtil.PromptType promptType,
61                              @NotNull final JComponent component) {
62     super.addPromptPanel(parent, promptNumber, promptType, component);
63     if (component instanceof IpnbPanel) {
64       myOutputPanels.add((IpnbPanel)component);
65     }
66   }
67
68   @Override
69   protected JComponent createViewPanel() {
70     final JPanel panel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP));
71     panel.setBackground(IpnbEditorUtil.getBackground());
72     panel.add(createCodeComponent());
73     panel.add(createHideableOutputPanel());
74
75     return panel;
76   }
77
78   @NotNull
79   private JPanel createCodeComponent() {
80     myCodeSourcePanel = new IpnbCodeSourcePanel(myProject, this, myCell);
81     final JPanel panel = new JPanel(new GridBagLayout());
82     panel.setBackground(IpnbEditorUtil.getBackground());
83     addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.In, myCodeSourcePanel);
84     final JPanel topComponent = new JPanel(new BorderLayout());
85     topComponent.add(panel, BorderLayout.PAGE_START);
86     return topComponent;
87   }
88
89   public JPanel createHideableOutputPanel() {
90     final OnePixelSplitter splitter = new OnePixelSplitter(true);
91     final JPanel secondComponent = createOutputPanel(splitter);
92     splitter.setSecondComponent(secondComponent);
93
94     return splitter;
95   }
96
97   @NotNull
98   private JPanel createOutputPanel(@NotNull OnePixelSplitter splitter) {
99     final MouseAdapter hideOutputListener = createHideOutputListener(splitter);
100     final JPanel outputPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, true, false));
101     outputPanel.setBackground(IpnbEditorUtil.getBackground());
102     for (IpnbOutputCell outputCell : myCell.getCellOutputs()) {
103       addOutputPanel(outputPanel, outputCell, hideOutputListener, true);
104     }
105
106     outputPanel.addMouseListener(hideOutputListener);
107     return outputPanel;
108   }
109
110   @NotNull
111   private MouseAdapter createHideOutputListener(final OnePixelSplitter splitter) {
112     final JPanel toggleBar = createToggleBar(splitter);
113     return new MouseAdapter() {
114       @Override
115       public void mouseClicked(MouseEvent e) {
116         if (e.getClickCount() == 2) {
117           hideOutputPanel();
118         }
119       }
120
121       private void hideOutputPanel() {
122         setOutputStateInCell(true);
123         splitter.setFirstComponent(toggleBar);
124         splitter.setSecondComponent(null);
125       }
126     };
127   }
128
129   @NotNull
130   private MouseAdapter createShowOutputListener(final OnePixelSplitter splitter, final JPanel secondPanel, JLabel label) {
131     return new MouseAdapter() {
132       @Override
133       public void mouseClicked(MouseEvent e) {
134         if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
135           showOutputPanel();
136         }
137       }
138
139       @Override
140       public void mouseEntered(MouseEvent e) {
141         updateBackground(UIUtil.getListSelectionBackground());
142       }
143
144       @Override
145       public void mouseExited(MouseEvent e) {
146         updateBackground(IpnbEditorUtil.getBackground());
147       }
148
149       private void updateBackground(Color background) {
150         secondPanel.setBackground(background);
151         label.setBackground(background);
152       }
153
154       private void showOutputPanel() {
155         setOutputStateInCell(false);
156         updateBackground(IpnbEditorUtil.getBackground());
157         splitter.setFirstComponent(null);
158         splitter.setSecondComponent(createOutputPanel(splitter));
159       }
160     };
161   }
162
163   private void setOutputStateInCell(boolean isCollapsed) {
164     final Map<String, Object> metadata = myCell.getMetadata();
165     if (metadata != null) {
166       metadata.put("collapsed", isCollapsed);
167     }
168   }
169
170   private JPanel createToggleBar(OnePixelSplitter splitter) {
171     final JPanel panel = new JPanel(new BorderLayout());
172     final JLabel label = new JLabel(AllIcons.Actions.Down);
173     panel.setBackground(IpnbEditorUtil.getBackground());
174     label.setBackground(IpnbEditorUtil.getBackground());
175     panel.add(label, BorderLayout.CENTER);
176
177     panel.addMouseListener(createShowOutputListener(splitter, panel, label));
178
179     return panel;
180   }
181
182   private void addOutputPanel(@NotNull final JComponent mainPanel,
183                               @NotNull final IpnbOutputCell outputCell, MouseAdapter hideOutputListener, boolean addPrompt) {
184     final IpnbEditorUtil.PromptType promptType = addPrompt ? IpnbEditorUtil.PromptType.Out : IpnbEditorUtil.PromptType.None;
185     final JPanel panel = new JPanel(new GridBagLayout());
186     panel.setBackground(IpnbEditorUtil.getBackground());
187     if (outputCell instanceof IpnbImageOutputCell) {
188       addPromptPanel(panel, myCell.getPromptNumber(), promptType,
189                      new IpnbImagePanel((IpnbImageOutputCell)outputCell, hideOutputListener));
190     }
191     else if (outputCell instanceof IpnbHtmlOutputCell) {
192       addPromptPanel(panel, myCell.getPromptNumber(), promptType,
193                      new IpnbHtmlPanel((IpnbHtmlOutputCell)outputCell, myParent.getIpnbFilePanel(), hideOutputListener));
194     }
195     else if (outputCell instanceof IpnbLatexOutputCell) {
196       addPromptPanel(panel, myCell.getPromptNumber(), promptType,
197                      new IpnbLatexPanel((IpnbLatexOutputCell)outputCell, myParent.getIpnbFilePanel(), hideOutputListener));
198     }
199     else if (outputCell instanceof IpnbErrorOutputCell) {
200       addPromptPanel(panel, myCell.getPromptNumber(), promptType,
201                      new IpnbErrorPanel((IpnbErrorOutputCell)outputCell, hideOutputListener));
202     }
203     else if (outputCell instanceof IpnbStreamOutputCell) {
204       addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.None,
205                      new IpnbStreamPanel((IpnbStreamOutputCell)outputCell, hideOutputListener));
206     }
207     else if (outputCell.getSourceAsString() != null) {
208       addPromptPanel(panel, myCell.getPromptNumber(), promptType,
209                      new IpnbCodeOutputPanel<>(outputCell, myParent.getIpnbFilePanel(), hideOutputListener));
210     }
211     mainPanel.add(panel);
212   }
213
214   @Override
215   public void switchToEditing() {
216     setEditing(true);
217     final Container parent = getParent();
218     if (parent != null) {
219       parent.repaint();
220     }
221     UIUtil.requestFocus(myCodeSourcePanel.getEditor().getContentComponent());
222   }
223
224   @Override
225   public void runCell(boolean selectNext) {
226     mySelectNext = selectNext;
227     updateCellSource();
228     isRunning = true;
229     updatePanel(null, null);
230     final IpnbConnectionManager connectionManager = IpnbConnectionManager.getInstance(myProject);
231     connectionManager.executeCell(this);
232     setEditing(false);
233   }
234
235   @Override
236   public boolean isModified() {
237     return true;
238   }
239
240   @Override
241   public void updateCellSource() {
242     final Document document = myCodeSourcePanel.getEditor().getDocument();
243     final String text = document.getText();
244     myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(text)));
245   }
246
247   public void updatePanel(@Nullable final String replacementContent, @Nullable final List<IpnbOutputCell> outputContent) {
248     final Application application = ApplicationManager.getApplication();
249     application.invokeAndWait(() -> {
250       if (replacementContent != null) {
251         myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(replacementContent)));
252         application.runWriteAction(() -> myCodeSourcePanel.getEditor().getDocument().setText(replacementContent));
253       }
254       myCell.removeCellOutputs();
255       myViewPanel.removeAll();
256
257       isRunning = false;
258       if (outputContent != null) {
259         for (IpnbOutputCell output : outputContent) {
260           myCell.addCellOutput(output);
261         }
262       }
263
264       myViewPanel = createViewPanel();
265       final IpnbFilePanel filePanel = myParent.getIpnbFilePanel();
266       setEditing(false);
267       filePanel.revalidateAndRepaint();
268       if (mySelectNext && (replacementContent != null || outputContent != null)) {
269         filePanel.selectNext(this, true);
270       }
271     }, ModalityState.stateForComponent(this));
272   }
273
274   @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
275   @Override
276   protected Object clone() {
277     return new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)myCell.clone());
278   }
279 }