Escape should work in terminal the same way as for other windows (IDEA-116221)
[idea/community.git] / plugins / terminal / src / org / jetbrains / plugins / terminal / JBTabbedTerminalWidget.java
1 package org.jetbrains.plugins.terminal;
2
3 import com.google.common.base.Predicate;
4 import com.intellij.ide.dnd.DnDDropHandler;
5 import com.intellij.ide.dnd.DnDEvent;
6 import com.intellij.ide.dnd.DnDSupport;
7 import com.intellij.ide.dnd.TransferableWrapper;
8 import com.intellij.openapi.Disposable;
9 import com.intellij.openapi.actionSystem.ActionManager;
10 import com.intellij.openapi.actionSystem.AnAction;
11 import com.intellij.openapi.actionSystem.AnActionEvent;
12 import com.intellij.openapi.actionSystem.Presentation;
13 import com.intellij.openapi.fileEditor.impl.EditorTabbedContainer;
14 import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
15 import com.intellij.openapi.project.DumbAwareAction;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.util.Disposer;
18 import com.intellij.openapi.util.Pair;
19 import com.intellij.openapi.wm.IdeFocusManager;
20 import com.intellij.psi.PsiDirectory;
21 import com.intellij.psi.PsiFile;
22 import com.intellij.psi.PsiFileSystemItem;
23 import com.intellij.ui.SimpleColoredComponent;
24 import com.intellij.ui.components.JBTextField;
25 import com.intellij.ui.docking.DockManager;
26 import com.intellij.ui.docking.DragSession;
27 import com.intellij.ui.tabs.TabInfo;
28 import com.intellij.ui.tabs.TabsListener;
29 import com.intellij.ui.tabs.impl.JBEditorTabs;
30 import com.intellij.ui.tabs.impl.JBTabsImpl;
31 import com.intellij.ui.tabs.impl.TabLabel;
32 import com.intellij.util.ui.UIUtil;
33 import com.jediterm.terminal.ui.*;
34 import com.jediterm.terminal.ui.settings.TabbedSettingsProvider;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37 import org.jetbrains.plugins.terminal.vfs.TerminalSessionVirtualFileImpl;
38
39 import javax.swing.*;
40 import java.awt.*;
41 import java.awt.event.*;
42 import java.util.List;
43 import java.util.concurrent.CopyOnWriteArraySet;
44
45 /**
46  * @author traff
47  */
48 public class JBTabbedTerminalWidget extends TabbedTerminalWidget implements Disposable {
49
50   private Project myProject;
51   private final JBTerminalSystemSettingsProvider mySettingsProvider;
52   private Disposable myParent;
53
54   public JBTabbedTerminalWidget(@NotNull Project project,
55                                 @NotNull JBTerminalSystemSettingsProvider settingsProvider,
56                                 final @NotNull Predicate<Pair<TerminalWidget, String>> createNewSessionAction, @NotNull Disposable parent) {
57     super(settingsProvider, new Predicate<TerminalWidget>() {
58       @Override
59       public boolean apply(TerminalWidget input) {
60         return createNewSessionAction.apply(Pair.<TerminalWidget, String>create(input, null));
61       }
62     });
63     myProject = project;
64
65     mySettingsProvider = settingsProvider;
66     myParent = parent;
67
68     convertActions(this, getActions());
69
70     Disposer.register(parent, this);
71     Disposer.register(this, settingsProvider);
72
73     DnDSupport.createBuilder(this).setDropHandler(new DnDDropHandler() {
74       @Override
75       public void drop(DnDEvent event) {
76         if (event.getAttachedObject() instanceof TransferableWrapper) {
77           TransferableWrapper ao = (TransferableWrapper)event.getAttachedObject();
78           if (ao != null &&
79               ao.getPsiElements() != null &&
80               ao.getPsiElements().length == 1 &&
81               ao.getPsiElements()[0] instanceof PsiFileSystemItem) {
82             PsiFileSystemItem element = (PsiFileSystemItem)ao.getPsiElements()[0];
83             PsiDirectory dir = element instanceof PsiFile ? ((PsiFile)element).getContainingDirectory() : (PsiDirectory)element;
84
85             createNewSessionAction.apply(Pair.<TerminalWidget, String>create(JBTabbedTerminalWidget.this, dir.getVirtualFile().getPath()));
86           }
87         }
88       }
89     }
90
91     ).install();
92   }
93
94   public static void convertActions(@NotNull JComponent component,
95                                     @NotNull List<TerminalAction> actions) {
96     convertActions(component, actions, null);
97   }
98
99   public static void convertActions(@NotNull JComponent component,
100                                     @NotNull List<TerminalAction> actions,
101                                     @Nullable final Predicate<KeyEvent> elseAction) {
102     for (final TerminalAction action : actions) {
103       if (action.isHidden()) {
104         continue;
105       }
106       AnAction a = new DumbAwareAction() {
107         @Override
108         public void actionPerformed(AnActionEvent e) {
109           KeyEvent event = e.getInputEvent() instanceof KeyEvent ? (KeyEvent)e.getInputEvent() : null;
110           if (!action.perform(event)) {
111             if (elseAction != null) {
112               elseAction.apply(event);
113             }
114           }
115         }
116       };
117       a.registerCustomShortcutSet(action.getKeyCode(), action.getModifiers(), component);
118     }
119   }
120
121   @Override
122   protected JediTermWidget createInnerTerminalWidget(TabbedSettingsProvider settingsProvider) {
123     return new JBTerminalWidget(myProject, mySettingsProvider, myParent);
124   }
125
126   @Override
127   protected TerminalTabs createTabbedPane() {
128     return new JBTerminalTabs(myProject, myParent);
129   }
130
131   public class JBTerminalTabs implements TerminalTabs {
132     private final JBEditorTabs myTabs;
133
134     private TabInfo.DragOutDelegate myDragDelegate = new MyDragOutDelegate();
135
136     private final CopyOnWriteArraySet<TabChangeListener> myListeners = new CopyOnWriteArraySet<>();
137
138     public JBTerminalTabs(@NotNull Project project, @NotNull Disposable parent) {
139       final ActionManager actionManager = ActionManager.getInstance();
140       myTabs = new JBEditorTabs(project, actionManager, IdeFocusManager.getInstance(project), parent) {
141         @Override
142         protected TabLabel createTabLabel(TabInfo info) {
143           return new TerminalTabLabel(this, info);
144         }
145       };
146
147       myTabs.addListener(new TabsListener.Adapter() {
148         @Override
149         public void selectionChanged(TabInfo oldSelection, TabInfo newSelection) {
150           for (TabChangeListener each : myListeners) {
151             each.selectionChanged();
152           }
153         }
154
155         @Override
156         public void tabRemoved(TabInfo tabInfo) {
157           for (TabChangeListener each : myListeners) {
158             each.tabRemoved();
159           }
160         }
161       });
162
163       myTabs.setTabDraggingEnabled(true);
164     }
165
166     @Override
167     public int getSelectedIndex() {
168       return myTabs.getIndexOf(myTabs.getSelectedInfo());
169     }
170
171     @Override
172     public void setSelectedIndex(int index) {
173       myTabs.select(myTabs.getTabAt(index), true);
174     }
175
176     @Override
177     public void setTabComponentAt(int index, Component component) {
178       //nop
179     }
180
181     @Override
182     public int indexOfComponent(Component component) {
183       for (int i = 0; i<myTabs.getTabCount(); i++) {
184         if (component.equals(myTabs.getTabAt(i).getComponent())) {
185           return i;
186         }
187       }
188       
189       return -1;
190     }
191
192     @Override
193     public int indexOfTabComponent(Component component) {
194       return 0; //nop
195     }
196
197
198     private TabInfo getTabAt(int index) {
199       checkIndex(index);
200       return myTabs.getTabAt(index);
201     }
202
203     private void checkIndex(int index) {
204       if (index < 0 || index >= getTabCount()) {
205         throw new ArrayIndexOutOfBoundsException("tabCount=" + getTabCount() + " index=" + index);
206       }
207     }
208
209
210     @Override
211     public JediTermWidget getComponentAt(int i) {
212       return (JediTermWidget)getTabAt(i).getComponent();
213     }
214
215     @Override
216     public void addChangeListener(TabChangeListener listener) {
217       myListeners.add(listener);
218     }
219
220     @Override
221     public void setTitleAt(int index, String title) {
222       getTabAt(index).setText(title);
223     }
224
225     @Override
226     public void setSelectedComponent(JediTermWidget terminal) {
227       TabInfo info = myTabs.findInfo(terminal);
228       if (info != null) {
229         myTabs.select(info, true);
230       }
231     }
232
233     @Override
234     public JComponent getComponent() {
235       return myTabs.getComponent();
236     }
237
238     @Override
239     public int getTabCount() {
240       return myTabs.getTabCount();
241     }
242
243     @Override
244     public void addTab(String name, JediTermWidget terminal) {
245       myTabs.addTab(createTabInfo(name, terminal));
246     }
247
248     private TabInfo createTabInfo(String name, JediTermWidget terminal) {
249       TabInfo tabInfo = new TabInfo(terminal).setText(name).setDragOutDelegate(myDragDelegate);
250       return tabInfo
251         .setObject(new TerminalSessionVirtualFileImpl(tabInfo, terminal, mySettingsProvider));
252     }
253
254     public String getTitleAt(int i) {
255       return getTabAt(i).getText();
256     }
257
258     public void removeAll() {
259       myTabs.removeAllTabs();
260     }
261
262     @Override
263     public void remove(JediTermWidget terminal) {
264       TabInfo info = myTabs.findInfo(terminal);
265       if (info != null) {
266         myTabs.removeTab(info);
267       }
268     }
269
270     private class TerminalTabLabel extends TabLabel {
271       public TerminalTabLabel(final JBTabsImpl tabs, final TabInfo info) {
272         super(tabs, info);
273
274         setOpaque(false);
275
276         setFocusable(false);
277
278         SimpleColoredComponent label = myLabel;
279
280         //add more space between the label and the button
281         label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
282
283         label.addMouseListener(new MouseAdapter() {
284
285           @Override
286           public void mouseReleased(MouseEvent event) {
287             handleMouse(event);
288           }
289
290           @Override
291           public void mousePressed(MouseEvent event) {
292             handleMouse(event);
293           }
294
295           private void handleMouse(MouseEvent e) {
296             if (e.isPopupTrigger()) {
297               JPopupMenu menu = createPopup();
298               menu.show(e.getComponent(), e.getX(), e.getY());
299             }
300             else if (e.getButton() != MouseEvent.BUTTON2) {
301               myTabs.select(getInfo(), true);
302
303               if (e.getClickCount() == 2 && !e.isConsumed()) {
304                 e.consume();
305                 renameTab();
306               }
307             }
308           }
309
310           @Override
311           public void mouseClicked(MouseEvent e) {
312             if (e.getButton() == MouseEvent.BUTTON2) {
313               if (myTabs.getSelectedInfo() == info) {
314                 closeCurrentSession();
315               }
316               else {
317                 myTabs.select(info, true);
318               }
319             }
320           }
321         });
322       }
323
324       protected JPopupMenu createPopup() {
325         JPopupMenu popupMenu = new JPopupMenu();
326
327         TerminalAction.addToMenu(popupMenu, JBTabbedTerminalWidget.this);
328
329         JMenuItem rename = new JMenuItem("Rename Tab");
330
331         rename.addActionListener(new ActionListener() {
332           @Override
333           public void actionPerformed(ActionEvent actionEvent) {
334             renameTab();
335           }
336         });
337
338         popupMenu.add(rename);
339
340         return popupMenu;
341       }
342
343       private void renameTab() {
344         new TabRenamer() {
345           @Override
346           protected JTextField createTextField() {
347             JBTextField textField = new JBTextField() {
348               private int myMinimalWidth;
349
350               @Override
351               public Dimension getPreferredSize() {
352                 Dimension size = super.getPreferredSize();
353                 if (size.width > myMinimalWidth) {
354                   myMinimalWidth = size.width;
355                 }
356
357                 return wider(size, myMinimalWidth);
358               }
359
360               private Dimension wider(Dimension size, int minimalWidth) {
361                 return new Dimension(minimalWidth + 10, size.height);
362               }
363             };
364             if (myTabs.useSmallLabels()) {
365               textField.setFont(com.intellij.util.ui.UIUtil.getFont(UIUtil.FontSize.SMALL, textField.getFont()));
366             }
367             textField.setOpaque(true);
368             return textField;
369           }
370         }.install(getSelectedIndex(), getInfo().getText(), myLabel, new TabRenamer.RenameCallBack() {
371           @Override
372           public void setComponent(Component c) {
373             myTabs.setTabDraggingEnabled(!(c instanceof JBTextField));
374
375             setPlaceholderContent(true, (JComponent)c);
376           }
377
378           @Override
379           public void setNewName(int index, String name) {
380             setTitleAt(index, name);
381           }
382         });
383       }
384     }
385
386     class MyDragOutDelegate implements TabInfo.DragOutDelegate {
387
388       private TerminalSessionVirtualFileImpl myFile;
389       private DragSession mySession;
390
391       @Override
392       public void dragOutStarted(MouseEvent mouseEvent, TabInfo info) {
393         final TabInfo previousSelection = info.getPreviousSelection();
394         final Image img = JBTabsImpl.getComponentImage(info);
395         info.setHidden(true);
396         if (previousSelection != null) {
397           myTabs.select(previousSelection, true);
398         }
399
400         myFile = (TerminalSessionVirtualFileImpl)info.getObject();
401         Presentation presentation = new Presentation(info.getText());
402         presentation.setIcon(info.getIcon());
403         mySession = getDockManager()
404           .createDragSession(mouseEvent, new EditorTabbedContainer.DockableEditor(myProject, img, myFile, presentation,
405                                                                                   info.getComponent().getPreferredSize(), false));
406       }
407
408       private DockManager getDockManager() {
409         return DockManager.getInstance(myProject);
410       }
411
412       @Override
413       public void processDragOut(MouseEvent event, TabInfo source) {
414         mySession.process(event);
415       }
416
417       @Override
418       public void dragOutFinished(MouseEvent event, TabInfo source) {
419         myFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, Boolean.TRUE);
420
421
422         myTabs.removeTab(source);
423
424         mySession.process(event);
425
426         myFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, null);
427
428
429         myFile = null;
430         mySession = null;
431       }
432
433       @Override
434       public void dragOutCancelled(TabInfo source) {
435         source.setHidden(false);
436         if (mySession != null) {
437           mySession.cancel();
438         }
439
440         myFile = null;
441         mySession = null;
442       }
443     }
444   }
445 }