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.editor.event.EditorMouseAdapter;
12 import com.intellij.openapi.editor.event.EditorMouseEvent;
13 import com.intellij.openapi.project.Project;
14 import com.intellij.openapi.ui.VerticalFlowLayout;
15 import com.intellij.openapi.ui.popup.ListPopup;
16 import com.intellij.openapi.util.TextRange;
17 import com.intellij.openapi.util.text.StringUtil;
18 import com.intellij.ui.OnePixelSplitter;
19 import com.intellij.ui.awt.RelativePoint;
20 import com.intellij.util.ui.UIUtil;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.Nullable;
23 import org.jetbrains.plugins.ipnb.configuration.IpnbConnectionManager;
24 import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
25 import org.jetbrains.plugins.ipnb.editor.IpnbFileEditor;
26 import org.jetbrains.plugins.ipnb.editor.actions.IpnbMergeCellAboveAction;
27 import org.jetbrains.plugins.ipnb.editor.actions.IpnbMergeCellBelowAction;
28 import org.jetbrains.plugins.ipnb.editor.actions.IpnbSplitCellAction;
29 import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel;
30 import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
31 import org.jetbrains.plugins.ipnb.editor.panels.IpnbPanel;
32 import org.jetbrains.plugins.ipnb.format.cells.IpnbCodeCell;
33 import org.jetbrains.plugins.ipnb.format.cells.output.*;
37 import java.awt.event.ActionEvent;
38 import java.awt.event.ActionListener;
39 import java.awt.event.MouseAdapter;
40 import java.awt.event.MouseEvent;
41 import java.util.Arrays;
42 import java.util.List;
45 public class IpnbCodePanel extends IpnbEditablePanel<JComponent, IpnbCodeCell> {
46 private final Project myProject;
47 @NotNull private final IpnbFileEditor myParent;
48 private final static String COLLAPSED_METADATA = "collapsed";
49 private IpnbCodeSourcePanel myCodeSourcePanel;
50 private final List<IpnbPanel> myOutputPanels = Lists.newArrayList();
51 private boolean mySelectNext;
53 public IpnbCodePanel(@NotNull final Project project, @NotNull final IpnbFileEditor parent, @NotNull final IpnbCodeCell cell) {
54 super(cell, new BorderLayout());
58 myViewPanel = createViewPanel();
64 public IpnbFileEditor getFileEditor() {
68 public Editor getEditor() {
69 return myCodeSourcePanel.getEditor();
72 public void addPromptPanel(@NotNull final JComponent parent, Integer promptNumber,
73 @NotNull final IpnbEditorUtil.PromptType promptType,
74 @NotNull final JComponent component) {
75 super.addPromptPanel(parent, promptNumber, promptType, component);
76 if (component instanceof IpnbPanel) {
77 myOutputPanels.add((IpnbPanel)component);
82 protected JComponent createViewPanel() {
83 final JPanel panel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP));
84 panel.setBackground(IpnbEditorUtil.getBackground());
85 panel.add(createCodeComponent());
86 panel.add(createHideableOutputPanel());
92 public void addRightClickMenu() {
93 myCodeSourcePanel.addMouseListener(new EditorMouseAdapter() {
95 public void mousePressed(EditorMouseEvent e) {
96 final MouseEvent mouseEvent = e.getMouseEvent();
97 if (SwingUtilities.isRightMouseButton(mouseEvent) && mouseEvent.getClickCount() == 1) {
98 final ListPopup menu = createClickMenu(new DefaultActionGroup(new IpnbMergeCellAboveAction(), new IpnbMergeCellBelowAction(),
99 new IpnbSplitCellAction()));
100 menu.show(RelativePoint.fromScreen(e.getMouseEvent().getLocationOnScreen()));
107 private JPanel createCodeComponent() {
108 myCodeSourcePanel = new IpnbCodeSourcePanel(myProject, this, myCell);
110 final JPanel panel = new JPanel(new GridBagLayout());
111 panel.setBackground(IpnbEditorUtil.getBackground());
112 addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.In, myCodeSourcePanel);
114 final JPanel topComponent = new JPanel(new BorderLayout());
115 topComponent.add(panel, BorderLayout.PAGE_START);
119 public JPanel createHideableOutputPanel() {
120 final OnePixelSplitter splitter = new OnePixelSplitter(true);
121 final JPanel toggleBar = createToggleBar(splitter);
122 final JPanel outputComponent = createOutputPanel(createHideOutputListener(splitter, toggleBar));
124 final Map<String, Object> metadata = myCell.getMetadata();
125 if (metadata.containsKey(COLLAPSED_METADATA)) {
126 final boolean isCollapsed = (Boolean)metadata.get(COLLAPSED_METADATA);
127 if (isCollapsed && !myCell.getCellOutputs().isEmpty()) {
128 splitter.setFirstComponent(toggleBar);
132 splitter.setSecondComponent(outputComponent);
138 private JPanel createOutputPanel(MouseAdapter hideOutputListener) {
139 final JPanel outputPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, true, false));
140 outputPanel.setBackground(IpnbEditorUtil.getBackground());
142 for (IpnbOutputCell outputCell : myCell.getCellOutputs()) {
143 addOutputPanel(outputPanel, outputCell, hideOutputListener, true);
146 if (!myCell.getCellOutputs().isEmpty()) {
147 outputPanel.addMouseListener(hideOutputListener);
154 private MouseAdapter createHideOutputListener(final OnePixelSplitter splitter, final JPanel toggleBar) {
155 return new MouseAdapter() {
156 private static final String TOGGLE_OUTPUT_TEXT = " Toggle output (Double-Click)";
159 public void mouseClicked(MouseEvent e) {
160 if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
166 public void mousePressed(MouseEvent e) {
167 if (SwingUtilities.isRightMouseButton(e) && e.getClickCount() == 1) {
168 final JPopupMenu menu = new JPopupMenu("");
170 final JMenuItem item = new JMenuItem(TOGGLE_OUTPUT_TEXT);
171 item.addActionListener(new ActionListener() {
173 public void actionPerformed(ActionEvent e) {
179 menu.show(e.getComponent(), e.getX(), e.getY());
183 private void hideOutputPanel() {
184 setOutputStateInCell(true);
185 splitter.setFirstComponent(toggleBar);
186 splitter.setSecondComponent(null);
192 private MouseAdapter createShowOutputListener(final OnePixelSplitter splitter, final JPanel secondPanel, JLabel label) {
193 return new MouseAdapter() {
195 public void mouseClicked(MouseEvent e) {
196 if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
202 public void mouseEntered(MouseEvent e) {
203 updateBackground(UIUtil.getListSelectionBackground());
207 public void mouseExited(MouseEvent e) {
208 updateBackground(IpnbEditorUtil.getBackground());
211 private void updateBackground(Color background) {
212 secondPanel.setBackground(background);
213 label.setBackground(background);
216 private void showOutputPanel() {
217 setOutputStateInCell(false);
218 updateBackground(IpnbEditorUtil.getBackground());
219 splitter.setFirstComponent(null);
220 final JPanel outputPanel = createOutputPanel(createHideOutputListener(splitter, IpnbCodePanel.this.createToggleBar(splitter)));
221 splitter.setSecondComponent(outputPanel);
226 private void setOutputStateInCell(boolean isCollapsed) {
227 final Map<String, Object> metadata = myCell.getMetadata();
228 metadata.put("collapsed", isCollapsed);
231 private JPanel createToggleBar(OnePixelSplitter splitter) {
232 final JPanel panel = new JPanel(new BorderLayout());
233 final JLabel label = new JLabel(AllIcons.Actions.Down);
234 panel.setBackground(IpnbEditorUtil.getBackground());
235 label.setBackground(IpnbEditorUtil.getBackground());
236 panel.add(label, BorderLayout.CENTER);
238 panel.addMouseListener(createShowOutputListener(splitter, panel, label));
243 private void addOutputPanel(@NotNull final JComponent mainPanel,
244 @NotNull final IpnbOutputCell outputCell, MouseAdapter hideOutputListener, boolean addPrompt) {
245 final IpnbEditorUtil.PromptType promptType = addPrompt ? IpnbEditorUtil.PromptType.Out : IpnbEditorUtil.PromptType.None;
246 final JPanel panel = new JPanel(new GridBagLayout());
247 panel.setBackground(IpnbEditorUtil.getBackground());
248 if (outputCell instanceof IpnbImageOutputCell) {
249 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
250 new IpnbImagePanel((IpnbImageOutputCell)outputCell, hideOutputListener));
252 else if (outputCell instanceof IpnbHtmlOutputCell) {
253 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
254 new IpnbHtmlPanel((IpnbHtmlOutputCell)outputCell, myParent.getIpnbFilePanel(), hideOutputListener));
256 else if (outputCell instanceof IpnbLatexOutputCell) {
257 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
258 new IpnbLatexPanel((IpnbLatexOutputCell)outputCell, myParent.getIpnbFilePanel(), hideOutputListener));
260 else if (outputCell instanceof IpnbErrorOutputCell) {
261 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
262 new IpnbErrorPanel((IpnbErrorOutputCell)outputCell, hideOutputListener));
264 else if (outputCell instanceof IpnbStreamOutputCell) {
265 addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.None,
266 new IpnbStreamPanel((IpnbStreamOutputCell)outputCell, hideOutputListener));
268 else if (outputCell.getSourceAsString() != null) {
269 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
270 new IpnbCodeOutputPanel<>(outputCell, myParent.getIpnbFilePanel(), hideOutputListener));
272 mainPanel.add(panel);
276 public void switchToEditing() {
278 final Container parent = getParent();
279 if (parent != null) {
282 UIUtil.requestFocus(myCodeSourcePanel.getEditor().getContentComponent());
286 public void runCell(boolean selectNext) {
287 mySelectNext = selectNext;
290 updatePanel(null, null);
291 final IpnbConnectionManager connectionManager = IpnbConnectionManager.getInstance(myProject);
292 connectionManager.executeCell(this);
297 public boolean isModified() {
302 public void updateCellSource() {
303 final Document document = myCodeSourcePanel.getEditor().getDocument();
304 final String text = document.getText();
305 myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(text)));
308 public void updatePanel(@Nullable final String replacementContent, @Nullable final List<IpnbOutputCell> outputContent) {
309 final Application application = ApplicationManager.getApplication();
310 application.invokeAndWait(() -> {
311 if (replacementContent != null) {
312 myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(replacementContent)));
313 application.runWriteAction(() -> myCodeSourcePanel.getEditor().getDocument().setText(replacementContent));
315 myCell.removeCellOutputs();
316 myViewPanel.removeAll();
319 if (outputContent != null) {
320 for (IpnbOutputCell output : outputContent) {
321 myCell.addCellOutput(output);
325 final JComponent panel = createViewPanel();
326 myViewPanel.add(panel);
328 final IpnbFilePanel filePanel = myParent.getIpnbFilePanel();
330 filePanel.revalidateAndRepaint();
331 if (mySelectNext && (replacementContent != null || outputContent != null)) {
332 filePanel.selectNext(this, true);
334 }, ModalityState.stateForComponent(this));
338 public void updateCellView() {
339 myViewPanel.removeAll();
340 final JComponent panel = createViewPanel();
341 myViewPanel.add(panel);
343 final IpnbFilePanel filePanel = myParent.getIpnbFilePanel();
344 filePanel.revalidate();
349 public int getCaretPosition() {
350 return myCodeSourcePanel.getEditor().getCaretModel().getOffset();
355 public String getText(int from, int to) {
356 return myCodeSourcePanel.getEditor().getDocument().getText(new TextRange(from, to));
360 public String getText(int from) {
361 return getText(from, myCodeSourcePanel.getEditor().getDocument().getTextLength());
364 @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
366 protected Object clone() {
367 return new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)myCell.clone());