bab43d35d93c29cdddc2d15cc894b3cf47ccca3f
[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       AnAction a = new DumbAwareAction() {
104         @Override
105         public void actionPerformed(AnActionEvent e) {
106           KeyEvent event = e.getInputEvent() instanceof KeyEvent ? (KeyEvent)e.getInputEvent() : null;
107           if (!action.perform(event)) {
108             if (elseAction != null) {
109               elseAction.apply(event);
110             }
111           }
112         }
113       };
114       a.registerCustomShortcutSet(action.getKeyCode(), action.getModifiers(), component);
115     }
116   }
117
118   @Override
119   protected JediTermWidget createInnerTerminalWidget(TabbedSettingsProvider settingsProvider) {
120     return new JBTerminalWidget(mySettingsProvider, myParent);
121   }
122
123   @Override
124   protected TerminalTabs createTabbedPane() {
125     return new JBTerminalTabs(myProject, myParent);
126   }
127
128   public class JBTerminalTabs implements TerminalTabs {
129     private final JBEditorTabs myTabs;
130
131     private TabInfo.DragOutDelegate myDragDelegate = new MyDragOutDelegate();
132
133     private final CopyOnWriteArraySet<TabChangeListener> myListeners = new CopyOnWriteArraySet<>();
134
135     public JBTerminalTabs(@NotNull Project project, @NotNull Disposable parent) {
136       final ActionManager actionManager = ActionManager.getInstance();
137       myTabs = new JBEditorTabs(project, actionManager, IdeFocusManager.getInstance(project), parent) {
138         @Override
139         protected TabLabel createTabLabel(TabInfo info) {
140           return new TerminalTabLabel(this, info);
141         }
142       };
143
144       myTabs.addListener(new TabsListener.Adapter() {
145         @Override
146         public void selectionChanged(TabInfo oldSelection, TabInfo newSelection) {
147           for (TabChangeListener each : myListeners) {
148             each.selectionChanged();
149           }
150         }
151
152         @Override
153         public void tabRemoved(TabInfo tabInfo) {
154           for (TabChangeListener each : myListeners) {
155             each.tabRemoved();
156           }
157         }
158       });
159
160       myTabs.setTabDraggingEnabled(true);
161     }
162
163     @Override
164     public int getSelectedIndex() {
165       return myTabs.getIndexOf(myTabs.getSelectedInfo());
166     }
167
168     @Override
169     public void setSelectedIndex(int index) {
170       myTabs.select(myTabs.getTabAt(index), true);
171     }
172
173     @Override
174     public void setTabComponentAt(int index, Component component) {
175       //nop
176     }
177
178     @Override
179     public int indexOfComponent(Component component) {
180       for (int i = 0; i<myTabs.getTabCount(); i++) {
181         if (component.equals(myTabs.getTabAt(i).getComponent())) {
182           return i;
183         }
184       }
185       
186       return -1;
187     }
188
189     @Override
190     public int indexOfTabComponent(Component component) {
191       return 0; //nop
192     }
193
194
195     private TabInfo getTabAt(int index) {
196       checkIndex(index);
197       return myTabs.getTabAt(index);
198     }
199
200     private void checkIndex(int index) {
201       if (index < 0 || index >= getTabCount()) {
202         throw new ArrayIndexOutOfBoundsException("tabCount=" + getTabCount() + " index=" + index);
203       }
204     }
205
206
207     @Override
208     public JediTermWidget getComponentAt(int i) {
209       return (JediTermWidget)getTabAt(i).getComponent();
210     }
211
212     @Override
213     public void addChangeListener(TabChangeListener listener) {
214       myListeners.add(listener);
215     }
216
217     @Override
218     public void setTitleAt(int index, String title) {
219       getTabAt(index).setText(title);
220     }
221
222     @Override
223     public void setSelectedComponent(JediTermWidget terminal) {
224       TabInfo info = myTabs.findInfo(terminal);
225       if (info != null) {
226         myTabs.select(info, true);
227       }
228     }
229
230     @Override
231     public JComponent getComponent() {
232       return myTabs.getComponent();
233     }
234
235     @Override
236     public int getTabCount() {
237       return myTabs.getTabCount();
238     }
239
240     @Override
241     public void addTab(String name, JediTermWidget terminal) {
242       myTabs.addTab(createTabInfo(name, terminal));
243     }
244
245     private TabInfo createTabInfo(String name, JediTermWidget terminal) {
246       TabInfo tabInfo = new TabInfo(terminal).setText(name).setDragOutDelegate(myDragDelegate);
247       return tabInfo
248         .setObject(new TerminalSessionVirtualFileImpl(tabInfo, terminal, mySettingsProvider));
249     }
250
251     public String getTitleAt(int i) {
252       return getTabAt(i).getText();
253     }
254
255     public void removeAll() {
256       myTabs.removeAllTabs();
257     }
258
259     @Override
260     public void remove(JediTermWidget terminal) {
261       TabInfo info = myTabs.findInfo(terminal);
262       if (info != null) {
263         myTabs.removeTab(info);
264       }
265     }
266
267     private class TerminalTabLabel extends TabLabel {
268       public TerminalTabLabel(final JBTabsImpl tabs, final TabInfo info) {
269         super(tabs, info);
270
271         setOpaque(false);
272
273         setFocusable(false);
274
275         SimpleColoredComponent label = myLabel;
276
277         //add more space between the label and the button
278         label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
279
280         label.addMouseListener(new MouseAdapter() {
281
282           @Override
283           public void mouseReleased(MouseEvent event) {
284             handleMouse(event);
285           }
286
287           @Override
288           public void mousePressed(MouseEvent event) {
289             handleMouse(event);
290           }
291
292           private void handleMouse(MouseEvent e) {
293             if (e.isPopupTrigger()) {
294               JPopupMenu menu = createPopup();
295               menu.show(e.getComponent(), e.getX(), e.getY());
296             }
297             else if (e.getButton() != MouseEvent.BUTTON2) {
298               myTabs.select(getInfo(), true);
299
300               if (e.getClickCount() == 2 && !e.isConsumed()) {
301                 e.consume();
302                 renameTab();
303               }
304             }
305           }
306
307           @Override
308           public void mouseClicked(MouseEvent e) {
309             if (e.getButton() == MouseEvent.BUTTON2) {
310               if (myTabs.getSelectedInfo() == info) {
311                 closeCurrentSession();
312               }
313               else {
314                 myTabs.select(info, true);
315               }
316             }
317           }
318         });
319       }
320
321       protected JPopupMenu createPopup() {
322         JPopupMenu popupMenu = new JPopupMenu();
323
324         TerminalAction.addToMenu(popupMenu, JBTabbedTerminalWidget.this);
325
326         JMenuItem rename = new JMenuItem("Rename Tab");
327
328         rename.addActionListener(new ActionListener() {
329           @Override
330           public void actionPerformed(ActionEvent actionEvent) {
331             renameTab();
332           }
333         });
334
335         popupMenu.add(rename);
336
337         return popupMenu;
338       }
339
340       private void renameTab() {
341         new TabRenamer() {
342           @Override
343           protected JTextField createTextField() {
344             JBTextField textField = new JBTextField() {
345               private int myMinimalWidth;
346
347               @Override
348               public Dimension getPreferredSize() {
349                 Dimension size = super.getPreferredSize();
350                 if (size.width > myMinimalWidth) {
351                   myMinimalWidth = size.width;
352                 }
353
354                 return wider(size, myMinimalWidth);
355               }
356
357               private Dimension wider(Dimension size, int minimalWidth) {
358                 return new Dimension(minimalWidth + 10, size.height);
359               }
360             };
361             if (myTabs.useSmallLabels()) {
362               textField.setFont(com.intellij.util.ui.UIUtil.getFont(UIUtil.FontSize.SMALL, textField.getFont()));
363             }
364             textField.setOpaque(true);
365             return textField;
366           }
367         }.install(getSelectedIndex(), getInfo().getText(), myLabel, new TabRenamer.RenameCallBack() {
368           @Override
369           public void setComponent(Component c) {
370             myTabs.setTabDraggingEnabled(!(c instanceof JBTextField));
371
372             setPlaceholderContent(true, (JComponent)c);
373           }
374
375           @Override
376           public void setNewName(int index, String name) {
377             setTitleAt(index, name);
378           }
379         });
380       }
381     }
382
383     class MyDragOutDelegate implements TabInfo.DragOutDelegate {
384
385       private TerminalSessionVirtualFileImpl myFile;
386       private DragSession mySession;
387
388       @Override
389       public void dragOutStarted(MouseEvent mouseEvent, TabInfo info) {
390         final TabInfo previousSelection = info.getPreviousSelection();
391         final Image img = JBTabsImpl.getComponentImage(info);
392         info.setHidden(true);
393         if (previousSelection != null) {
394           myTabs.select(previousSelection, true);
395         }
396
397         myFile = (TerminalSessionVirtualFileImpl)info.getObject();
398         Presentation presentation = new Presentation(info.getText());
399         presentation.setIcon(info.getIcon());
400         mySession = getDockManager()
401           .createDragSession(mouseEvent, new EditorTabbedContainer.DockableEditor(myProject, img, myFile, presentation,
402                                                                                   info.getComponent().getPreferredSize(), false));
403       }
404
405       private DockManager getDockManager() {
406         return DockManager.getInstance(myProject);
407       }
408
409       @Override
410       public void processDragOut(MouseEvent event, TabInfo source) {
411         mySession.process(event);
412       }
413
414       @Override
415       public void dragOutFinished(MouseEvent event, TabInfo source) {
416         myFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, Boolean.TRUE);
417
418
419         myTabs.removeTab(source);
420
421         mySession.process(event);
422
423         myFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, null);
424
425
426         myFile = null;
427         mySession = null;
428       }
429
430       @Override
431       public void dragOutCancelled(TabInfo source) {
432         source.setHidden(false);
433         if (mySession != null) {
434           mySession.cancel();
435         }
436
437         myFile = null;
438         mySession = null;
439       }
440     }
441   }
442 }