Merge branch 'master' into changeSignature
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / EditorTabbedContainer.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.fileEditor.impl;
17
18 import com.intellij.ide.IdeEventQueue;
19 import com.intellij.ide.actions.CloseAction;
20 import com.intellij.ide.actions.ShowFilePathAction;
21 import com.intellij.ide.ui.UISettings;
22 import com.intellij.ide.ui.customization.CustomActionsSchema;
23 import com.intellij.openapi.Disposable;
24 import com.intellij.openapi.actionSystem.*;
25 import com.intellij.openapi.editor.colors.EditorColorsManager;
26 import com.intellij.openapi.extensions.Extensions;
27 import com.intellij.openapi.fileEditor.FileEditor;
28 import com.intellij.openapi.fileEditor.FileEditorManager;
29 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
30 import com.intellij.openapi.project.DumbAware;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.ui.Queryable;
33 import com.intellij.openapi.ui.ShadowAction;
34 import com.intellij.openapi.util.*;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.openapi.wm.IdeFocusManager;
37 import com.intellij.openapi.wm.ToolWindowAnchor;
38 import com.intellij.openapi.wm.ToolWindowManager;
39 import com.intellij.openapi.wm.ex.ToolWindowManagerAdapter;
40 import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
41 import com.intellij.ui.SimpleTextAttributes;
42 import com.intellij.ui.switcher.SwitchProvider;
43 import com.intellij.ui.switcher.SwitchTarget;
44 import com.intellij.ui.tabs.*;
45 import com.intellij.ui.tabs.impl.JBTabsImpl;
46 import com.intellij.util.ui.AwtVisitor;
47 import com.intellij.util.ui.TimedDeadzone;
48 import com.intellij.util.ui.UIUtil;
49 import org.jetbrains.annotations.NonNls;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52
53 import javax.swing.*;
54 import javax.swing.border.EmptyBorder;
55 import java.awt.*;
56 import java.awt.event.MouseAdapter;
57 import java.awt.event.MouseEvent;
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.Map;
61
62 /**
63  * @author Anton Katilin
64  * @author Vladimir Kondratyev
65  */
66 final class EditorTabbedContainer implements Disposable, CloseAction.CloseTarget {
67   private final EditorWindow myWindow;
68   private final Project myProject;
69   private final JBTabs myTabs;
70
71   @NonNls public static final String HELP_ID = "ideaInterface.editor";
72
73   EditorTabbedContainer(final EditorWindow window, Project project, int tabPlacement) {
74     myWindow = window;
75     myProject = project;
76     final ActionManager actionManager = ActionManager.getInstance();
77     myTabs = new JBTabsImpl(project, actionManager, IdeFocusManager.getInstance(project), this);
78     myTabs.setDataProvider(new MyDataProvider()).setPopupGroup(new Getter<ActionGroup>() {
79       public ActionGroup get() {
80         return (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_EDITOR_TAB_POPUP);
81       }
82     }, ActionPlaces.EDITOR_POPUP, false).setNavigationActionsEnabled(false).addTabMouseListener(new TabMouseListener()).getPresentation().
83       setTabDraggingEnabled(true).setUiDecorator(new UiDecorator() {
84       @NotNull
85       public UiDecoration getDecoration() {
86         return new UiDecoration(null, new Insets(1, 6, 1, 6));
87       }
88     }).setTabLabelActionsMouseDeadzone(TimedDeadzone.NULL).setGhostsAlwaysVisible(true).setTabLabelActionsAutoHide(false)
89       .setActiveTabFillIn(EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground()).setPaintFocus(false).getJBTabs()
90       .addListener(new TabsListener.Adapter() {
91         public void selectionChanged(final TabInfo oldSelection, final TabInfo newSelection) {
92           final FileEditorManager editorManager = FileEditorManager.getInstance(myProject);
93           final FileEditor oldEditor = oldSelection != null ? editorManager.getSelectedEditor((VirtualFile)oldSelection.getObject()) : null;
94           if (oldEditor != null) {
95             oldEditor.deselectNotify();
96           }
97
98           final FileEditor newEditor = editorManager.getSelectedEditor((VirtualFile)newSelection.getObject());
99           if (newEditor != null) {
100             newEditor.selectNotify();
101           }
102         }
103       }).setAdditinalSwitchProviderWhenOriginal(new MySwitchProvider());
104
105     setTabPlacement(UISettings.getInstance().EDITOR_TAB_PLACEMENT);
106
107     updateTabBorder();
108
109     ((ToolWindowManagerEx)ToolWindowManager.getInstance(myProject)).addToolWindowManagerListener(new ToolWindowManagerAdapter() {
110       public void stateChanged() {
111         updateTabBorder();
112       }
113
114       public void toolWindowRegistered(@NotNull final String id) {
115         updateTabBorder();
116       }
117     });
118
119     Disposer.register(project, this);
120   }
121
122   public int getTabCount() {
123     return myTabs.getTabCount();
124   }
125
126   public ActionCallback setSelectedIndex(final int indexToSelect) {
127     return setSelectedIndex(indexToSelect, true);
128   }
129
130   public ActionCallback setSelectedIndex(final int indexToSelect, boolean focusEditor) {
131     return myTabs.select(myTabs.getTabAt(indexToSelect), focusEditor);
132   }
133
134   private void updateTabBorder() {
135     if (!myProject.isOpen()) return;
136
137     myTabs.getComponent().setBorder(new EmptyBorder(1, 0, 0, 0));
138     final List<String> rightIds = ((ToolWindowManagerEx)ToolWindowManager.getInstance(myProject)).getIdsOn(ToolWindowAnchor.RIGHT);
139     myTabs.getPresentation().setPaintBorder(-1, -1, rightIds.size() > 0 ? 1 : 0, -1).setTabSidePaintBorder(5);
140   }
141
142   public Component getComponent() {
143     return myTabs.getComponent();
144   }
145
146   public ActionCallback removeTabAt(final int componentIndex, int indexToSelect, boolean transferFocus) {
147     TabInfo toSelect = indexToSelect >= 0 && indexToSelect < myTabs.getTabCount() ? myTabs.getTabAt(indexToSelect) : null;
148     final ActionCallback callback = myTabs.removeTab(myTabs.getTabAt(componentIndex), toSelect, transferFocus);
149     return myProject.isOpen() ? callback : new ActionCallback.Done();
150   }
151
152   public ActionCallback removeTabAt(final int componentIndex, int indexToSelect) {
153     return removeTabAt(componentIndex, indexToSelect, true);
154   }
155
156   public int getSelectedIndex() {
157     return myTabs.getIndexOf(myTabs.getSelectedInfo());
158   }
159
160   public void setForegroundAt(final int index, final Color color) {
161     myTabs.getTabAt(index).setDefaultForeground(color);
162   }
163
164   public void setWaveColor(final int index, @Nullable final Color color) {
165     final TabInfo tab = myTabs.getTabAt(index);
166     tab.setDefaultStyle(color == null ? SimpleTextAttributes.STYLE_PLAIN : SimpleTextAttributes.STYLE_WAVED);
167     tab.setDefaultWaveColor(color);
168   }
169
170   public void setIconAt(final int index, final Icon icon) {
171     myTabs.getTabAt(index).setIcon(icon);
172   }
173
174   public void setTitleAt(final int index, final String text) {
175     myTabs.getTabAt(index).setText(text);
176   }
177
178   public void setToolTipTextAt(final int index, final String text) {
179     myTabs.getTabAt(index).setTooltipText(text);
180   }
181
182   public void setBackgroundColorAt(final int index, final Color color) {
183     myTabs.getTabAt(index).setTabColor(color);
184   }
185
186   public void setTabLayoutPolicy(final int policy) {
187     switch (policy) {
188       case JTabbedPane.SCROLL_TAB_LAYOUT:
189         myTabs.getPresentation().setSingleRow(true);
190         break;
191       case JTabbedPane.WRAP_TAB_LAYOUT:
192         myTabs.getPresentation().setSingleRow(false);
193         break;
194       default:
195         throw new IllegalArgumentException("Unsupported tab layout policy: " + policy);
196     }
197   }
198
199   public void setTabPlacement(final int tabPlacement) {
200     switch (tabPlacement) {
201       case SwingConstants.TOP:
202         myTabs.getPresentation().setTabsPosition(JBTabsPosition.top);
203         break;
204       case SwingConstants.BOTTOM:
205         myTabs.getPresentation().setTabsPosition(JBTabsPosition.bottom);
206         break;
207       case SwingConstants.LEFT:
208         myTabs.getPresentation().setTabsPosition(JBTabsPosition.left);
209         break;
210       case SwingConstants.RIGHT:
211         myTabs.getPresentation().setTabsPosition(JBTabsPosition.right);
212         break;
213       default:
214         throw new IllegalArgumentException("Unknown tab placement code=" + tabPlacement);
215     }
216   }
217
218   @Nullable
219   public Object getSelectedComponent() {
220     final TabInfo info = myTabs.getTargetInfo();
221     return info != null ? info.getComponent() : null;
222   }
223
224   public void insertTab(final VirtualFile file, final Icon icon, final JComponent comp, final String tooltip, final int indexToInsert) {
225
226     TabInfo tab = myTabs.findInfo(file);
227     if (tab != null) return;
228
229     tab = new TabInfo(comp).setText(calcTabTitle(myProject, file)).setIcon(icon).setTooltipText(tooltip).setObject(file)
230       .setTabColor(calcTabColor(myProject, file));
231     tab.setTestableUi(new MyQueryable(tab));
232
233     final DefaultActionGroup tabActions = new DefaultActionGroup();
234     tabActions.add(new CloseTab(comp, tab));
235
236     tab.setTabLabelActions(tabActions, ActionPlaces.EDITOR_TAB);
237     myTabs.addTab(tab, indexToInsert);
238   }
239
240   private class MyQueryable implements Queryable {
241
242     private final TabInfo myTab;
243
244     public MyQueryable(TabInfo tab) {
245       myTab = tab;
246     }
247
248     public void putInfo(Map<String, String> info) {
249       info.put("editorTab", myTab.getText());
250     }
251   }
252
253   public static String calcTabTitle(final Project project, final VirtualFile file) {
254     for (EditorTabTitleProvider provider : Extensions.getExtensions(EditorTabTitleProvider.EP_NAME)) {
255       final String result = provider.getEditorTabTitle(project, file);
256       if (result != null) {
257         return result;
258       }
259     }
260
261     return file.getPresentableName();
262   }
263
264   public static Color calcTabColor(final Project project, final VirtualFile file) {
265     for (EditorTabColorProvider provider : Extensions.getExtensions(EditorTabColorProvider.EP_NAME)) {
266       final Color result = provider.getEditorTabColor(project, file);
267       if (result != null) {
268         return result;
269       }
270     }
271
272     return null;
273   }
274
275   public Component getComponentAt(final int i) {
276     final TabInfo tab = myTabs.getTabAt(i);
277     return tab.getComponent();
278   }
279
280   public void dispose() {
281
282   }
283
284   private class CloseTab extends AnAction implements DumbAware {
285
286     ShadowAction myShadow;
287     private final TabInfo myTabInfo;
288     private final Icon myIcon = IconLoader.getIcon("/actions/close.png");
289     private final Icon myHoveredIcon = IconLoader.getIcon("/actions/closeHovered.png");
290
291     public CloseTab(JComponent c, TabInfo info) {
292       myTabInfo = info;
293       myShadow = new ShadowAction(this, ActionManager.getInstance().getAction(IdeActions.ACTION_CLOSE), c);
294     }
295
296     @Override
297     public void update(final AnActionEvent e) {
298       e.getPresentation().setIcon(myIcon);
299       e.getPresentation().setHoveredIcon(myHoveredIcon);
300       e.getPresentation().setVisible(UISettings.getInstance().SHOW_CLOSE_BUTTON);
301     }
302
303     public void actionPerformed(final AnActionEvent e) {
304       final FileEditorManagerEx mgr = FileEditorManagerEx.getInstanceEx(myProject);
305       EditorWindow window;
306       final VirtualFile file = (VirtualFile)myTabInfo.getObject();
307       if (ActionPlaces.EDITOR_TAB.equals(e.getPlace())) {
308         window = myWindow;
309       }
310       else {
311         window = mgr.getCurrentWindow();
312       }
313
314       if (window != null) {
315         if (window.findFileComposite(file) != null) {
316           mgr.closeFile(file, window);
317         }
318       }
319     }
320   }
321
322   private class MyDataProvider implements DataProvider {
323     public Object getData(@NonNls final String dataId) {
324       if (PlatformDataKeys.PROJECT.is(dataId)) {
325         return myProject;
326       }
327       if (PlatformDataKeys.VIRTUAL_FILE.is(dataId)) {
328         final VirtualFile selectedFile = myWindow.getSelectedFile();
329         return selectedFile != null && selectedFile.isValid() ? selectedFile : null;
330       }
331       if (EditorWindow.DATA_KEY.is(dataId)) {
332         return myWindow;
333       }
334       if (PlatformDataKeys.HELP_ID.is(dataId)) {
335         return HELP_ID;
336       }
337
338       if (CloseAction.CloseTarget.KEY.is(dataId)) {
339         TabInfo selected = myTabs.getSelectedInfo();
340         if (selected != null) {
341           return EditorTabbedContainer.this;
342         }
343       }
344
345       return null;
346     }
347   }
348
349   public void close() {
350     TabInfo selected = myTabs.getSelectedInfo();
351     if (selected == null) return;
352
353     VirtualFile file = (VirtualFile)selected.getObject();
354     final FileEditorManagerEx mgr = FileEditorManagerEx.getInstanceEx(myProject);
355     EditorWindow wnd = mgr.getCurrentWindow();
356     if (wnd != null) {
357       if (wnd.findFileComposite(file) != null) {
358         mgr.closeFile(file, wnd);
359       }
360     }
361   }
362
363   private class TabMouseListener extends MouseAdapter {
364     @Override
365     public void mousePressed(final MouseEvent e) {
366       if (UIUtil.isCloseClick(e, MouseEvent.MOUSE_PRESSED)) {
367         final TabInfo info = myTabs.findInfo(e);
368         if (info != null) {
369           IdeEventQueue.getInstance().blockNextEvents(e);
370           myWindow.closeFile((VirtualFile)info.getObject());
371           return;
372         }
373       }
374
375       if (UIUtil.isActionClick(e) && (e.getClickCount() % 2) == 0) {
376         final ActionManager mgr = ActionManager.getInstance();
377         mgr.tryToExecute(mgr.getAction("HideAllWindows"), e, null, ActionPlaces.UNKNOWN, true);
378       }
379       else if (UIUtil.isActionClick(e) && (e.isMetaDown() || (!SystemInfo.isMac && e.isControlDown()))) {
380         final TabInfo info = myTabs.findInfo(e);
381         if (info != null && info.getObject() != null) {
382           final VirtualFile vFile = (VirtualFile)info.getObject();
383           ShowFilePathAction.show(vFile, e);
384         }
385       }
386     }
387   }
388
389   private class MySwitchProvider implements SwitchProvider {
390     public List<SwitchTarget> getTargets(final boolean onlyVisible, boolean originalProvider) {
391       final ArrayList<SwitchTarget> result = new ArrayList<SwitchTarget>();
392       TabInfo selected = myTabs.getSelectedInfo();
393       new AwtVisitor(selected.getComponent()) {
394         @Override
395         public boolean visit(Component component) {
396           if (component instanceof JBTabs) {
397             JBTabs tabs = (JBTabs)component;
398             if (tabs != myTabs) {
399               result.addAll(tabs.getTargets(onlyVisible, false));
400               return true;
401             }
402           }
403           return false;
404         }
405       };
406       return result;
407     }
408
409     public SwitchTarget getCurrentTarget() {
410       TabInfo selected = myTabs.getSelectedInfo();
411       final Ref<SwitchTarget> targetRef = new Ref<SwitchTarget>();
412       new AwtVisitor(selected.getComponent()) {
413         @Override
414         public boolean visit(Component component) {
415           if (component instanceof JBTabs) {
416             JBTabs tabs = (JBTabs)component;
417             if (tabs != myTabs) {
418               targetRef.set(tabs.getCurrentTarget());
419               return true;
420             }
421           }
422           return false;
423         }
424       };
425
426       return targetRef.get();
427     }
428
429     public JComponent getComponent() {
430       return null;
431     }
432
433     public boolean isCycleRoot() {
434       return false;
435     }
436   }
437 }