PY-19960 PY-14096 Merge right-click menu creation for toggle output and split/merge...
[idea/community.git] / python / ipnb / src / org / jetbrains / plugins / ipnb / editor / panels / IpnbEditablePanel.java
1 package org.jetbrains.plugins.ipnb.editor.panels;
2
3 import com.intellij.openapi.actionSystem.DefaultActionGroup;
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.ui.popup.ListPopup;
6 import com.intellij.openapi.util.text.StringUtil;
7 import com.intellij.ui.JBColor;
8 import com.intellij.ui.awt.RelativePoint;
9 import com.intellij.ui.OnePixelSplitter;
10 import com.intellij.util.ui.JBUI;
11 import com.intellij.util.ui.UIUtil;
12 import org.jetbrains.annotations.NotNull;
13 import org.jetbrains.annotations.Nullable;
14 import org.jetbrains.plugins.ipnb.editor.IpnbEditorUtil;
15 import org.jetbrains.plugins.ipnb.editor.actions.IpnbMergeCellAboveAction;
16 import org.jetbrains.plugins.ipnb.editor.actions.IpnbMergeCellBelowAction;
17 import org.jetbrains.plugins.ipnb.editor.actions.IpnbSplitCellAction;
18 import org.jetbrains.plugins.ipnb.format.cells.IpnbEditableCell;
19
20 import javax.swing.*;
21 import javax.swing.text.BadLocationException;
22 import javax.swing.text.Document;
23 import java.awt.*;
24 import java.awt.event.KeyAdapter;
25 import java.awt.event.KeyEvent;
26 import java.awt.event.MouseAdapter;
27 import java.awt.event.MouseEvent;
28 import java.util.Arrays;
29
30 public abstract class IpnbEditablePanel<T extends JComponent, K extends IpnbEditableCell> extends IpnbPanel<T, K> {
31   private static final Logger LOG = Logger.getInstance(IpnbEditablePanel.class);
32   private boolean myEditing;
33   protected JTextArea myEditablePanel;
34   public final static String EDITABLE_PANEL = "Editable panel";
35   public final static String VIEW_PANEL = "View panel";
36   protected boolean isRunning = false;
37   private OnePixelSplitter mySplitter;
38   private JPanel myViewPrompt;
39   private JPanel myEditablePrompt;
40
41   public IpnbEditablePanel(@NotNull K cell) {
42     super(cell);
43   }
44
45   public IpnbEditablePanel(@NotNull K cell, @NotNull final LayoutManager layoutManager) {
46     super(cell, layoutManager);
47   }
48
49   protected void initPanel() {
50     mySplitter = new OnePixelSplitter(true);
51     addViewPanel();
52     addEditablePanel();
53     mySplitter.setFirstComponent(myViewPrompt);
54     mySplitter.setSecondComponent(null);
55     setBackground(IpnbEditorUtil.getBackground());
56     add(mySplitter);
57     addRightClickMenu();
58   }
59
60   private void addEditablePanel() {
61     myEditablePanel = createEditablePanel();
62     myEditablePrompt = new JPanel(new GridBagLayout());
63
64     myEditablePrompt.setName(EDITABLE_PANEL);
65     myEditablePrompt.setBackground(IpnbEditorUtil.getBackground());
66     addPromptPanel(myEditablePrompt, null, IpnbEditorUtil.PromptType.None, myEditablePanel);
67   }
68
69   private void addViewPanel() {
70     myViewPanel = createViewPanel();
71     myViewPanel.addMouseListener(new MouseAdapter() {
72       @Override
73       public void mouseClicked(MouseEvent e) {
74         final Container parent = getParent();
75         final MouseEvent parentEvent = SwingUtilities.convertMouseEvent(myViewPanel, e, parent);
76         parent.dispatchEvent(parentEvent);
77         if (e.getClickCount() == 2) {
78           switchToEditing();
79         }
80       }
81     });
82     myViewPanel.setName(VIEW_PANEL);
83
84     myViewPrompt = new JPanel(new GridBagLayout());
85     addPromptPanel(myViewPrompt, null, IpnbEditorUtil.PromptType.None, myViewPanel);
86     myViewPrompt.setBackground(IpnbEditorUtil.getBackground());
87   }
88
89   public void addPromptPanel(@NotNull final JComponent parent, Integer promptNumber,
90                              @NotNull final IpnbEditorUtil.PromptType promptType,
91                              @NotNull final JComponent component) {
92     final GridBagConstraints c = new GridBagConstraints();
93     c.fill = GridBagConstraints.HORIZONTAL;
94     c.gridx = 0;
95     c.gridy = 0;
96     c.gridwidth = 1;
97
98     c.weightx = 0;
99     c.anchor = GridBagConstraints.NORTHWEST;
100     Integer number = promptNumber;
101     if (isRunning) {
102       number = -1;
103     }
104
105     final JComponent promptComponent = IpnbEditorUtil.createPromptComponent(number, promptType);
106     c.insets = JBUI.insets(2, 2, 2, 5);
107     parent.add(promptComponent, c);
108
109     c.gridx = 1;
110     c.weightx = 1;
111     c.insets = JBUI.insets(2);
112     c.anchor = GridBagConstraints.CENTER;
113     parent.add(component, c);
114   }
115
116
117   public void switchToEditing() {
118     setEditing(true);
119
120     mySplitter.setFirstComponent(myEditablePrompt);
121     UIUtil.requestFocus(myEditablePanel);
122     mySplitter.setSecondComponent(null);
123   }
124
125   public boolean isModified() {
126     final Component[] components = getComponents();
127     for (Component component : components) {
128       final String name = component.getName();
129       if (component.isVisible() && EDITABLE_PANEL.equals(name)) return true;
130     }
131     return false;
132   }
133
134   protected String getRawCellText() { return ""; }
135
136   public void runCell(boolean selectNext) {
137     if (mySplitter != null) {
138       updateCellSource();
139       updateCellView();
140       mySplitter.setFirstComponent(myViewPrompt);
141       mySplitter.setSecondComponent(null);
142       setEditing(false);
143       final Container parent = getParent();
144       if (parent instanceof IpnbFilePanel) {
145         UIUtil.requestFocus((IpnbFilePanel)parent);
146         if (selectNext) {
147           ((IpnbFilePanel)parent).selectNext(this, true);
148         }
149       }
150     }
151   }
152
153   private JTextArea createEditablePanel() {
154     final JTextArea textArea = new JTextArea(getRawCellText());
155     textArea.setLineWrap(true);
156     textArea.setEditable(true);
157     textArea.setBorder(BorderFactory.createLineBorder(JBColor.lightGray));
158     textArea.setBackground(IpnbEditorUtil.getEditablePanelBackground());
159     textArea.addMouseListener(new MouseAdapter() {
160       @Override
161       public void mouseClicked(MouseEvent e) {
162         if (e.getClickCount() == 1) {
163           setEditing(true);
164           final Container parent = getParent();
165           parent.repaint();
166           if (parent instanceof IpnbFilePanel) {
167             ((IpnbFilePanel)parent).setSelectedCellPanel(IpnbEditablePanel.this);
168             textArea.requestFocus();
169           }
170         }
171       }
172     });
173     textArea.addKeyListener(new KeyAdapter() {
174       @Override
175       public void keyReleased(KeyEvent e) {
176         if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
177           setEditing(false);
178           final Container parent = getParent();
179           if (parent instanceof IpnbFilePanel) {
180             parent.repaint();
181             UIUtil.requestFocus((IpnbFilePanel)parent);
182           }
183         }
184       }
185     });
186     return textArea;
187   }
188
189   public boolean contains(int y) {
190     return y >= getTop() && y <= getBottom();
191   }
192
193   public int getTop() {
194     return getY();
195   }
196
197   public int getBottom() {
198     return getTop() + getHeight();
199   }
200
201   public boolean isEditing() {
202     return myEditing;
203   }
204
205   public void setEditing(boolean editing) {
206     myEditing = editing;
207   }
208
209   public void updateCellView() {
210   }
211
212   public int getCaretPosition() {
213     return (myEditing && myEditablePanel != null) ? myEditablePanel.getCaretPosition() : -1;
214   }
215
216   @Override
217   protected void addRightClickMenu() {
218     myViewPanel.addMouseListener(new MouseAdapter() {
219       @Override
220       public void mousePressed(MouseEvent e) {
221         if (SwingUtilities.isRightMouseButton(e) && e.getClickCount() == 1) {
222           final DefaultActionGroup group = new DefaultActionGroup(new IpnbMergeCellAboveAction(), new IpnbMergeCellBelowAction());
223           final ListPopup menu = createPopupMenu(group);
224           menu.show(RelativePoint.fromScreen(e.getLocationOnScreen()));
225         }
226       }
227     });
228     myEditablePanel.addMouseListener(new MouseAdapter() {
229       @Override
230       public void mousePressed(MouseEvent e) {
231         if (SwingUtilities.isRightMouseButton(e) && e.getClickCount() == 1) {
232           final DefaultActionGroup group = new DefaultActionGroup(new IpnbSplitCellAction());
233           final ListPopup menu = createPopupMenu(group);
234           menu.show(RelativePoint.fromScreen(e.getLocationOnScreen()));
235         }
236       }
237     });
238   }
239
240   
241
242   @Nullable
243   public String getText(int from, int to) {
244     if (myEditing && myEditablePanel != null) {
245       try {
246         return myEditablePanel.getDocument().getText(from, to - from);
247       }
248       catch (BadLocationException e) {
249         LOG.warn(e.getMessage());
250       }
251     }
252     return null;
253   }
254
255   public String getText(int from) {
256     if (myEditing && myEditablePanel != null) {
257       final Document document = myEditablePanel.getDocument();
258       final int to = document.getLength();
259       return getText(from, to);
260     }
261     return null;
262   }
263
264   public void updateCellSource() {
265     final String text = myEditablePanel.getText();
266     myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(text != null ? text : "")));
267   }
268
269   @SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException")
270   @Override
271   protected Object clone() {
272     try {
273       return super.clone();
274     }
275     catch (CloneNotSupportedException e) {
276       LOG.error(e);
277     }
278     return null;
279   }
280
281   public K getCell() {
282     return myCell;
283   }
284
285   public JTextArea getEditablePanel() {
286     return myEditablePanel;
287   }
288 }