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);
131 final JPanel panel = new JPanel(new GridBagLayout());
132 panel.setBackground(IpnbEditorUtil.getBackground());
133 addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.In, myCodeSourcePanel);
135 final JPanel topComponent = new JPanel(new BorderLayout());
136 topComponent.add(panel, BorderLayout.PAGE_START);
141 private JPanel createOutputPanel() {
142 final JPanel outputPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, true, false));
143 outputPanel.setBackground(IpnbEditorUtil.getBackground());
145 for (IpnbOutputCell outputCell : myCell.getCellOutputs()) {
146 addOutputPanel(outputPanel, outputCell, this, true);
154 public void hideOutputPanel() {
155 myHideableOutputPanel.hideOutputPanel();
159 private MouseAdapter createShowOutputListener(final OnePixelSplitter splitter, final JPanel secondPanel, JLabel label) {
160 return new MouseAdapter() {
162 public void mouseClicked(MouseEvent e) {
163 if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
169 public void mouseEntered(MouseEvent e) {
170 updateBackground(UIUtil.getListSelectionBackground());
174 public void mouseExited(MouseEvent e) {
175 updateBackground(IpnbEditorUtil.getBackground());
178 private void updateBackground(Color background) {
179 secondPanel.setBackground(background);
180 label.setBackground(background);
183 private void showOutputPanel() {
184 setOutputStateInCell(false);
185 updateBackground(IpnbEditorUtil.getBackground());
186 splitter.setFirstComponent(null);
187 final JPanel outputPanel = createOutputPanel();
188 splitter.setSecondComponent(outputPanel);
193 private void setOutputStateInCell(boolean isCollapsed) {
194 final Map<String, Object> metadata = myCell.getMetadata();
195 metadata.put("collapsed", isCollapsed);
198 private JPanel createToggleBar(OnePixelSplitter splitter) {
199 final JPanel panel = new JPanel(new BorderLayout());
200 final JLabel label = new JLabel(AllIcons.Actions.Down);
201 panel.setBackground(IpnbEditorUtil.getBackground());
202 label.setBackground(IpnbEditorUtil.getBackground());
203 panel.add(label, BorderLayout.CENTER);
205 panel.addMouseListener(createShowOutputListener(splitter, panel, label));
210 private void addOutputPanel(@NotNull final JComponent mainPanel,
211 @NotNull final IpnbOutputCell outputCell, IpnbCodePanel ipnbCodePanel, boolean addPrompt) {
212 final IpnbEditorUtil.PromptType promptType = addPrompt ? IpnbEditorUtil.PromptType.Out : IpnbEditorUtil.PromptType.None;
213 final JPanel panel = new JPanel(new GridBagLayout());
214 panel.setBackground(IpnbEditorUtil.getBackground());
215 if (outputCell instanceof IpnbImageOutputCell) {
216 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
217 new IpnbImagePanel((IpnbImageOutputCell)outputCell, ipnbCodePanel));
219 else if (outputCell instanceof IpnbHtmlOutputCell) {
220 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
221 new IpnbHtmlPanel((IpnbHtmlOutputCell)outputCell, myParent.getIpnbFilePanel(), ipnbCodePanel));
223 else if (outputCell instanceof IpnbLatexOutputCell) {
224 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
225 new IpnbLatexPanel((IpnbLatexOutputCell)outputCell, myParent.getIpnbFilePanel(), ipnbCodePanel));
227 else if (outputCell instanceof IpnbErrorOutputCell) {
228 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
229 new IpnbErrorPanel((IpnbErrorOutputCell)outputCell, ipnbCodePanel));
231 else if (outputCell instanceof IpnbStreamOutputCell) {
232 addPromptPanel(panel, myCell.getPromptNumber(), IpnbEditorUtil.PromptType.None,
233 new IpnbStreamPanel((IpnbStreamOutputCell)outputCell, ipnbCodePanel));
235 else if (outputCell.getSourceAsString() != null) {
236 addPromptPanel(panel, myCell.getPromptNumber(), promptType,
237 new IpnbCodeOutputPanel<>(outputCell, myParent.getIpnbFilePanel(), ipnbCodePanel));
239 mainPanel.add(panel);
243 public void switchToEditing() {
245 final Container parent = getParent();
246 if (parent != null) {
249 UIUtil.requestFocus(myCodeSourcePanel.getEditor().getContentComponent());
253 public void runCell(boolean selectNext) {
254 mySelectNext = selectNext;
257 updatePanel(null, null);
258 final IpnbConnectionManager connectionManager = IpnbConnectionManager.getInstance(myProject);
259 connectionManager.executeCell(this);
264 public boolean isModified() {
269 public void updateCellSource() {
270 final Document document = myCodeSourcePanel.getEditor().getDocument();
271 final String text = document.getText();
272 myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(text)));
275 public void updatePanel(@Nullable final String replacementContent, @Nullable final List<IpnbOutputCell> outputContent) {
276 final Application application = ApplicationManager.getApplication();
277 application.invokeAndWait(() -> {
278 if (replacementContent != null) {
279 myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(replacementContent)));
280 application.runWriteAction(() -> myCodeSourcePanel.getEditor().getDocument().setText(replacementContent));
282 myCell.removeCellOutputs();
283 myViewPanel.removeAll();
286 if (outputContent != null) {
287 for (IpnbOutputCell output : outputContent) {
288 myCell.addCellOutput(output);
292 final JComponent panel = createViewPanel();
293 myViewPanel.add(panel);
295 final IpnbFilePanel filePanel = myParent.getIpnbFilePanel();
297 filePanel.revalidateAndRepaint();
298 if (mySelectNext && (replacementContent != null || outputContent != null)) {
299 filePanel.selectNext(this, true);
301 }, ModalityState.stateForComponent(this));
305 public void updateCellView() {
306 myViewPanel.removeAll();
307 final JComponent panel = createViewPanel();
308 myViewPanel.add(panel);
310 final IpnbFilePanel filePanel = myParent.getIpnbFilePanel();
311 filePanel.revalidate();
316 public int getCaretPosition() {
317 return myCodeSourcePanel.getEditor().getCaretModel().getOffset();
322 public String getText(int from, int to) {
323 return myCodeSourcePanel.getEditor().getDocument().getText(new TextRange(from, to));
327 public String getText(int from) {
328 return getText(from, myCodeSourcePanel.getEditor().getDocument().getTextLength());
331 @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
333 protected Object clone() {
334 return new IpnbCodePanel(myProject, myParent, (IpnbCodeCell)myCell.clone());