1 package org.jetbrains.plugins.ipnb.editor.panels.code;
3 import com.google.common.collect.Lists;
4 import com.intellij.icons.AllIcons;
5 import com.intellij.openapi.actionSystem.DefaultActionGroup;
6 import com.intellij.openapi.application.Application;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.application.ModalityState;
9 import com.intellij.openapi.editor.Document;
10 import com.intellij.openapi.editor.Editor;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.ui.VerticalFlowLayout;
13 import com.intellij.openapi.ui.popup.ListPopup;
14 import com.intellij.openapi.util.TextRange;
15 import com.intellij.openapi.util.text.StringUtil;
16 import com.intellij.ui.OnePixelSplitter;
17 import com.intellij.ui.awt.RelativePoint;
18 import com.intellij.util.ui.UIUtil;
19 import org.jetbrains.annotations.NotNull;
20 import org.jetbrains.annotations.Nullable;
21 import org.jetbrains.plugins.ipnb.configuration.IpnbConnectionManager;
22 import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
23 import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
24 import org.jetbrains.plugins.ipnb.editor.actions.IpnbHideOutputAction;
25 import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel;
26 import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
27 import org.jetbrains.plugins.ipnb.editor.panels.IpnbPanel;
28 import org.jetbrains.plugins.ipnb.format.cells.IpnbCodeCell;
29 import org.jetbrains.plugins.ipnb.format.cells.output.*;
33 import java.awt.event.MouseAdapter;
34 import java.awt.event.MouseEvent;
35 import java.util.Arrays;
36 import java.util.List;
39 public class IpnbCodePanel extends IpnbEditablePanel<JComponent, IpnbCodeCell> {
40 private final Project myProject;
41 @NotNull private final IpnbFileEditor myParent;
42 private final static String COLLAPSED_METADATA = "collapsed";
43 private IpnbCodeSourcePanel myCodeSourcePanel;
44 private final List<IpnbPanel> myOutputPanels = Lists.newArrayList();
45 private HideableOutputPanel myHideableOutputPanel;
46 private boolean mySelectNext;
48 public IpnbCodePanel(@NotNull final Project project, @NotNull final IpnbFileEditor parent, @NotNull final IpnbCodeCell cell) {
49 super(cell, new BorderLayout());
53 myViewPanel = createViewPanel();
59 public IpnbFileEditor getFileEditor() {
63 public Editor getEditor() {
64 return myCodeSourcePanel.getEditor();
67 public void addPromptPanel(@NotNull final JComponent parent, Integer promptNumber,
68 @NotNull final IpnbEditorUtil.PromptType promptType,
69 @NotNull final JComponent component) {
70 super.addPromptPanel(parent, promptNumber, promptType, component);
71 if (component instanceof IpnbPanel) {
72 myOutputPanels.add((IpnbPanel)component);
77 protected JComponent createViewPanel() {
78 final JPanel panel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP));
79 panel.setBackground(IpnbEditorUtil.getBackground());
80 panel.add(createCodeComponent());
81 myHideableOutputPanel = new HideableOutputPanel();
82 panel.add(myHideableOutputPanel);
88 protected void addRightClickMenu() {
89 myHideableOutputPanel.addMouseListener(new MouseAdapter() {
91 public void mousePressed(MouseEvent e) {
92 if (SwingUtilities.isRightMouseButton(e) && e.getClickCount() == 1) {
93 final ListPopup menu = createPopupMenu(new DefaultActionGroup(new IpnbHideOutputAction(IpnbCodePanel.this)));
94 menu.show(RelativePoint.fromScreen(e.getLocationOnScreen()));
100 class HideableOutputPanel extends OnePixelSplitter{
101 final JPanel myToggleBar;
102 final JPanel myOutputComponent;
104 public HideableOutputPanel() {
106 myToggleBar = createToggleBar(this);
107 myOutputComponent = createOutputPanel();
109 final Map<String, Object> metadata = myCell.getMetadata();
110 if (metadata.containsKey(COLLAPSED_METADATA)) {
111 final boolean isCollapsed = (Boolean)metadata.get(COLLAPSED_METADATA);
112 if (isCollapsed && !myCell.getCellOutputs().isEmpty()) {
113 setFirstComponent(myToggleBar);
117 setSecondComponent(myOutputComponent);
120 public void hideOutputPanel() {
121 setOutputStateInCell(true);
122 setFirstComponent(myToggleBar);
123 setSecondComponent(null);
128 private JPanel createCodeComponent() {
129 myCodeSourcePanel = new IpnbCodeSourcePanel(myProject, this, myCell);
130 final JPanel panel = new JPanel(new GridBagLayout());
131 panel.setBackground(IpnbEditorUtil.getBackground());
132 addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.In, myCodeSourcePanel);
134 final JPanel topComponent = new JPanel(new BorderLayout());
135 topComponent.add(panel, BorderLayout.PAGE_START);
140 private JPanel createOutputPanel() {
141 final JPanel outputPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, true, false));
142 outputPanel.setBackground(IpnbEditorUtil.getBackground());
144 for (IpnbOutputCell outputCell : myCell.getCellOutputs()) {
145 addOutputPanel(outputPanel, outputCell, true);
153 public void hideOutputPanel() {
154 myHideableOutputPanel.hideOutputPanel();
158 private MouseAdapter createShowOutputListener(final OnePixelSplitter splitter, final JPanel secondPanel, JLabel label) {
159 return new MouseAdapter() {
161 public void mouseClicked(MouseEvent e) {
162 if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
168 public void mouseEntered(MouseEvent e) {
169 updateBackground(UIUtil.getListSelectionBackground());
173 public void mouseExited(MouseEvent e) {
174 updateBackground(IpnbEditorUtil.getBackground());
177 private void updateBackground(Color background) {
178 secondPanel.setBackground(background);
179 label.setBackground(background);
182 private void showOutputPanel() {
183 setOutputStateInCell(false);
184 updateBackground(IpnbEditorUtil.getBackground());
185 splitter.setFirstComponent(null);
186 final JPanel outputPanel = createOutputPanel();
187 splitter.setSecondComponent(outputPanel);
192 private void setOutputStateInCell(boolean isCollapsed) {
193 final Map<String, Object> metadata = myCell.getMetadata();
194 metadata.put("collapsed", isCollapsed);
197 private JPanel createToggleBar(OnePixelSplitter splitter) {
198 final JPanel panel = new JPanel(new BorderLayout());
199 final JLabel label = new JLabel(AllIcons.Actions.Down);
200 panel.setBackground(IpnbEditorUtil.getBackground());
201 label.setBackground(IpnbEditorUtil.getBackground());
202 panel.add(label, BorderLayout.CENTER);
204 panel.addMouseListener(createShowOutputListener(splitter, panel, label));
209 private void addOutputPanel(@NotNull final JComponent mainPanel,
210 @NotNull final IpnbOutputCell outputCell, boolean addPrompt) {
211 final IpnbEditorUtil.PromptType promptType = addPrompt ? IpnbEditorUtil.PromptType.Out : IpnbEditorUtil.PromptType.None;
212 final JPanel panel = new JPanel(new GridBagLayout());
213 panel.setBackground(IpnbEditorUtil.getBackground());
214 if (outputCell instanceof IpnbImageOutputCell) {
215 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
216 new IpnbImagePanel((IpnbImageOutputCell)outputCell, this));
218 else if (outputCell instanceof IpnbHtmlOutputCell) {
219 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
220 new IpnbHtmlPanel((IpnbHtmlOutputCell)outputCell, myParent.getIpnbFilePanel(), this));
222 else if (outputCell instanceof IpnbLatexOutputCell) {
223 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
224 new IpnbLatexPanel((IpnbLatexOutputCell)outputCell, myParent.getIpnbFilePanel(), this));
226 else if (outputCell instanceof IpnbErrorOutputCell) {
227 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
228 new IpnbErrorPanel((IpnbErrorOutputCell)outputCell, this));
230 else if (outputCell instanceof IpnbStreamOutputCell) {
231 addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.None,
232 new IpnbStreamPanel((IpnbStreamOutputCell)outputCell, this));
234 else if (outputCell.getSourceAsString() != null) {
235 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
236 new IpnbCodeOutputPanel<>(outputCell, myParent.getIpnbFilePanel(), this));
238 mainPanel.add(panel);
242 public void switchToEditing() {
244 final Container parent = getParent();
245 if (parent != null) {
248 UIUtil.requestFocus(myCodeSourcePanel.getEditor().getContentComponent());
252 public void runCell(boolean selectNext) {
253 mySelectNext = selectNext;
256 final IpnbConnectionManager connectionManager = IpnbConnectionManager.getInstance(myProject);
257 connectionManager.executeCell(this);
262 public boolean isModified() {
267 public void updateCellSource() {
268 final Document document = myCodeSourcePanel.getEditor().getDocument();
269 final String text = document.getText();
270 myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(text)));
273 public void updatePrompt() {
274 final Application application = ApplicationManager.getApplication();
275 application.invokeAndWait(() -> {
276 myCell.setPromptNumber(-1);
277 myCell.removeCellOutputs();
278 myViewPanel.removeAll();
280 final JComponent panel = createViewPanel();
281 myViewPanel.add(panel);
282 }, ModalityState.stateForComponent(this));
285 public void finishExecution() {
286 final Application application = ApplicationManager.getApplication();
287 application.invokeAndWait(() -> {
288 final String promptText = IpnbEditorUtil.prompt(myCell.getPromptNumber(), IpnbEditorUtil.PromptType.In);
289 myPromptLabel.setText(promptText);
290 final IpnbFilePanel filePanel = myParent.getIpnbFilePanel();
292 filePanel.revalidateAndRepaint();
294 filePanel.selectNext(this, true);
296 }, ModalityState.stateForComponent(this));
299 public void updatePanel(@Nullable final String replacementContent, @Nullable final IpnbOutputCell outputContent) {
300 final Application application = ApplicationManager.getApplication();
301 application.invokeAndWait(() -> {
302 if (replacementContent != null) {
303 myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(replacementContent)));
304 String prompt = IpnbEditorUtil.prompt(null, IpnbEditorUtil.PromptType.In);
305 myCell.setPromptNumber(null);
306 myPromptLabel.setText(prompt);
307 application.runWriteAction(() -> myCodeSourcePanel.getEditor().getDocument().setText(replacementContent));
309 if (outputContent != null) {
310 myCell.addCellOutput(outputContent);
311 final JComponent component = myHideableOutputPanel.getSecondComponent();
312 if (component != null) {
313 addOutputPanel(component, outputContent, outputContent instanceof IpnbOutOutputCell);
316 final IpnbFilePanel filePanel = myParent.getIpnbFilePanel();
317 filePanel.revalidateAndRepaint();
318 }, ModalityState.stateForComponent(this));
322 public void updateCellView() {
323 myViewPanel.removeAll();
324 final JComponent panel = createViewPanel();
325 myViewPanel.add(panel);
327 final IpnbFilePanel filePanel = myParent.getIpnbFilePanel();
328 filePanel.revalidate();
333 public int getCaretPosition() {
334 return myCodeSourcePanel.getEditor().getCaretModel().getOffset();
339 public String getText(int from, int to) {
340 return myCodeSourcePanel.getEditor().getDocument().getText(new TextRange(from, to));
344 public String getText(int from) {
345 return getText(from, myCodeSourcePanel.getEditor().getDocument().getTextLength());
348 @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
350 protected Object clone() {
351 return new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)myCell.clone());