replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / content / TabbedPaneContentUI.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.ui.content;
17
18 import com.intellij.openapi.actionSystem.*;
19 import com.intellij.openapi.util.SystemInfo;
20 import com.intellij.ui.*;
21 import com.intellij.ui.content.tabs.PinToolwindowTabAction;
22 import com.intellij.ui.content.tabs.TabbedContentAction;
23 import com.intellij.util.IJSwingUtilities;
24 import org.jetbrains.annotations.NotNull;
25
26 import javax.swing.*;
27 import javax.swing.event.ChangeEvent;
28 import javax.swing.event.ChangeListener;
29 import javax.swing.plaf.TabbedPaneUI;
30 import java.awt.*;
31 import java.awt.event.MouseEvent;
32 import java.beans.PropertyChangeEvent;
33 import java.beans.PropertyChangeListener;
34 import java.util.List;
35
36 /**
37  * @author Eugene Belyaev
38  * @author Anton Katilin
39  * @author Vladimir Kondratyev
40  */
41 public class TabbedPaneContentUI implements ContentUI, PropertyChangeListener {
42   public static final String POPUP_PLACE = "TabbedPanePopup";
43
44   private ContentManager myManager;
45   private TabbedPaneWrapper myTabbedPaneWrapper;
46
47   /**
48    * Creates {@code TabbedPaneContentUI} with bottom tab placement.
49    */
50   public TabbedPaneContentUI() {
51     this(JTabbedPane.BOTTOM);
52   }
53
54   /**
55    * Creates {@code TabbedPaneContentUI} with cpecified tab placement.
56    *
57    * @param tabPlacement constant which defines where the tabs are located.
58    *                     Acceptable values are {@code javax.swing.JTabbedPane#TOP},
59    *                     {@code javax.swing.JTabbedPane#LEFT}, {@code javax.swing.JTabbedPane#BOTTOM}
60    *                     and {@code javax.swing.JTabbedPane#RIGHT}.
61    */
62   public TabbedPaneContentUI(int tabPlacement) {
63     myTabbedPaneWrapper = new MyTabbedPaneWrapper(tabPlacement);
64   }
65
66   public JComponent getComponent() {
67     return myTabbedPaneWrapper.getComponent();
68   }
69
70   public void setManager(@NotNull ContentManager manager) {
71     if (myManager != null) {
72       throw new IllegalStateException();
73     }
74     myManager = manager;
75     myManager.addContentManagerListener(new MyContentManagerListener());
76   }
77
78   public void propertyChange(PropertyChangeEvent e) {
79     if (Content.PROP_DISPLAY_NAME.equals(e.getPropertyName())) {
80       Content content = (Content)e.getSource();
81       int index = myTabbedPaneWrapper.indexOfComponent(content.getComponent());
82       if (index != -1) {
83         myTabbedPaneWrapper.setTitleAt(index, content.getTabName());
84       }
85     }
86     else if (Content.PROP_DESCRIPTION.equals(e.getPropertyName())) {
87       Content content = (Content)e.getSource();
88       int index = myTabbedPaneWrapper.indexOfComponent(content.getComponent());
89       if (index != -1) {
90         myTabbedPaneWrapper.setToolTipTextAt(index, content.getDescription());
91       }
92     }
93     else if (Content.PROP_COMPONENT.equals(e.getPropertyName())) {
94       Content content = (Content)e.getSource();
95       JComponent oldComponent = (JComponent)e.getOldValue();
96       int index = myTabbedPaneWrapper.indexOfComponent(oldComponent);
97       if (index != -1) {
98         boolean hasFocus = IJSwingUtilities.hasFocus2(oldComponent);
99         myTabbedPaneWrapper.setComponentAt(index, content.getComponent());
100         if (hasFocus) {
101           content.getComponent().requestDefaultFocus();
102         }
103       }
104     }
105     else if (Content.PROP_ICON.equals(e.getPropertyName())) {
106       Content content = (Content)e.getSource();
107       int index = myTabbedPaneWrapper.indexOfComponent(content.getComponent());
108       if (index != -1) {
109         myTabbedPaneWrapper.setIconAt(index, (Icon)e.getNewValue());
110       }
111     }
112   }
113
114   private Content getSelectedContent() {
115     JComponent selectedComponent = myTabbedPaneWrapper.getSelectedComponent();
116     return myManager.getContent(selectedComponent);
117   }
118
119
120
121
122   private class MyTabbedPaneWrapper extends TabbedPaneWrapper.AsJTabbedPane {
123     public MyTabbedPaneWrapper(int tabPlacement) {
124       super(tabPlacement);
125     }
126
127     protected TabbedPane createTabbedPane(int tabPlacement) {
128       return new MyTabbedPane(tabPlacement);
129     }
130
131     protected TabbedPaneHolder createTabbedPaneHolder() {
132       return new MyTabbedPaneHolder(this);
133     }
134
135     private class MyTabbedPane extends TabbedPaneImpl {
136       public MyTabbedPane(int tabPlacement) {
137         super(tabPlacement);
138         addMouseListener(new MyPopupHandler());
139         enableEvents(AWTEvent.MOUSE_EVENT_MASK);
140       }
141
142       private void closeTabAt(int x, int y) {
143         TabbedPaneUI ui = getUI();
144         int index = ui.tabForCoordinate(this, x, y);
145         if (index < 0 || !myManager.canCloseContents()) {
146           return;
147         }
148         final Content content = myManager.getContent(index);
149         if (content != null && content.isCloseable()) {
150           myManager.removeContent(content, true);
151         }
152       }
153
154       /**
155        * Hides selected menu.
156        */
157       private void hideMenu() {
158         MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
159         menuSelectionManager.clearSelectedPath();
160       }
161
162       protected void processMouseEvent(MouseEvent e) {
163         if (e.isPopupTrigger()) { // Popup doesn't activate clicked tab.
164           showPopup(e.getX(), e.getY());
165           return;
166         }
167
168         if (!e.isShiftDown() && (MouseEvent.BUTTON1_MASK & e.getModifiers()) > 0) { // RightClick without Shift modifiers just select tab
169           if (MouseEvent.MOUSE_RELEASED == e.getID()) {
170             TabbedPaneUI ui = getUI();
171             int index = ui.tabForCoordinate(this, e.getX(), e.getY());
172             if (index != -1) {
173               setSelectedIndex(index);
174             }
175             hideMenu();
176           }
177         }
178         else if (e.isShiftDown() && (MouseEvent.BUTTON1_MASK & e.getModifiers()) > 0) { // Shift+LeftClick closes the tab
179           if (MouseEvent.MOUSE_RELEASED == e.getID()) {
180             closeTabAt(e.getX(), e.getY());
181             hideMenu();
182           }
183         }
184         else if ((MouseEvent.BUTTON2_MASK & e.getModifiers()) > 0) { // MouseWheelClick closes the tab
185           if (MouseEvent.MOUSE_RELEASED == e.getID()) {
186             closeTabAt(e.getX(), e.getY());
187             hideMenu();
188           }
189         }
190         else if ((MouseEvent.BUTTON3_MASK & e.getModifiers()) > 0 && SystemInfo.isWindows) { // Right mouse button doesn't activate tab
191         }
192         else {
193           super.processMouseEvent(e);
194         }
195       }
196
197       protected ChangeListener createChangeListener() {
198         return new MyModelListener();
199       }
200
201       private class MyModelListener extends ModelListener {
202         public void stateChanged(ChangeEvent e) {
203           Content content = getSelectedContent();
204           if (content != null) {
205             myManager.setSelectedContent(content);
206           }
207           super.stateChanged(e);
208         }
209       }
210
211       /**
212        * @return content at the specified location.  {@code x} and {@code y} are in
213        *         tabbed pane coordinate system. The method returns {@code null} if there is no contnt at the
214        *         specified location.
215        */
216       private Content getContentAt(int x, int y) {
217         TabbedPaneUI ui = getUI();
218         int index = ui.tabForCoordinate(this, x, y);
219         if (index < 0) {
220           return null;
221         }
222         return myManager.getContent(index);
223       }
224
225       protected class MyPopupHandler extends PopupHandler {
226         public void invokePopup(Component comp, int x, int y) {
227           if (myManager.getContentCount() == 0) return;
228           showPopup(x, y);
229         }
230       }
231
232       /**
233        * Shows showPopup menu at the specified location. The {@code x} and {@code y} coordinates
234        * are in JTabbedPane coordinate system.
235        */
236       private void showPopup(int x, int y) {
237         Content content = getContentAt(x, y);
238         if (content == null) {
239           return;
240         }
241         DefaultActionGroup group = new DefaultActionGroup();
242         group.add(new TabbedContentAction.CloseAction(content));
243         if (myTabbedPaneWrapper.getTabCount() > 1) {
244           group.add(new TabbedContentAction.CloseAllAction(myManager));
245           group.add(new TabbedContentAction.CloseAllButThisAction(content));
246         }
247         group.addSeparator();
248         group.add(PinToolwindowTabAction.getPinAction());
249         group.addSeparator();
250         group.add(new TabbedContentAction.MyNextTabAction(myManager));
251         group.add(new TabbedContentAction.MyPreviousTabAction(myManager));
252         final List<AnAction> additionalActions = myManager.getAdditionalPopupActions(content);
253         if (additionalActions != null) {
254           group.addSeparator();
255           for (AnAction anAction : additionalActions) {
256             group.add(anAction);
257           }
258         }
259         ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(POPUP_PLACE, group);
260         menu.getComponent().show(myTabbedPaneWrapper.getComponent(), x, y);
261       }
262     }
263
264     private class MyTabbedPaneHolder extends TabbedPaneHolder implements DataProvider {
265
266       private MyTabbedPaneHolder(TabbedPaneWrapper wrapper) {
267         super(wrapper);
268       }
269
270       public Object getData(String dataId) {
271         if (PlatformDataKeys.CONTENT_MANAGER.is(dataId)) {
272           return myManager;
273         }
274         if (PlatformDataKeys.NONEMPTY_CONTENT_MANAGER.is(dataId) && myManager.getContentCount() > 1) {
275           return myManager;
276         }
277         return null;
278       }
279     }
280   }
281
282   private class MyContentManagerListener extends ContentManagerAdapter {
283     public void contentAdded(ContentManagerEvent event) {
284       Content content = event.getContent();
285       myTabbedPaneWrapper.insertTab(content.getTabName(),
286                                     content.getIcon(),
287                                     content.getComponent(),
288                                     content.getDescription(),
289                                     event.getIndex());
290       content.addPropertyChangeListener(TabbedPaneContentUI.this);
291     }
292
293     public void contentRemoved(ContentManagerEvent event) {
294       event.getContent().removePropertyChangeListener(TabbedPaneContentUI.this);
295       myTabbedPaneWrapper.removeTabAt(event.getIndex());
296     }
297
298     public void selectionChanged(ContentManagerEvent event) {
299       int index = event.getIndex();
300       if (index != -1 && event.getOperation() != ContentManagerEvent.ContentOperation.remove) {
301         myTabbedPaneWrapper.setSelectedIndex(index);
302       }
303     }
304   }
305
306   public boolean isSingleSelection() {
307     return true;
308   }
309
310   public boolean isToSelectAddedContent() {
311     return false;
312   }
313
314   public boolean canBeEmptySelection() {
315     return false;
316   }
317
318   public void beforeDispose() {
319   }
320
321   public boolean canChangeSelectionTo(@NotNull Content content, boolean implicit) {
322     return true;
323   }
324
325   @NotNull
326   @Override
327   public String getCloseActionName() {
328     return UIBundle.message("tabbed.pane.close.tab.action.name");
329   }
330
331   @NotNull
332   @Override
333   public String getCloseAllButThisActionName() {
334     return UIBundle.message("tabbed.pane.close.all.tabs.but.this.action.name");
335   }
336
337   @NotNull
338   @Override
339   public String getPreviousContentActionName() {
340     return "Select Previous Tab";
341   }
342
343   @NotNull
344   @Override
345   public String getNextContentActionName() {
346     return "Select Next Tab";
347   }
348
349   public void dispose() {
350   }
351 }