2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com
.intellij
.ui
;
18 import com
.intellij
.openapi
.Disposable
;
19 import com
.intellij
.openapi
.actionSystem
.DataProvider
;
20 import com
.intellij
.openapi
.actionSystem
.IdeActions
;
21 import com
.intellij
.openapi
.application
.ex
.ApplicationEx
;
22 import com
.intellij
.openapi
.application
.ex
.ApplicationManagerEx
;
23 import com
.intellij
.openapi
.project
.Project
;
24 import com
.intellij
.openapi
.wm
.ex
.IdeFocusTraversalPolicy
;
25 import com
.intellij
.ui
.tabs
.JBTabs
;
26 import com
.intellij
.util
.IJSwingUtilities
;
27 import org
.jetbrains
.annotations
.NotNull
;
28 import org
.jetbrains
.annotations
.Nullable
;
31 import javax
.swing
.event
.ChangeListener
;
33 import java
.awt
.event
.MouseListener
;
36 * @author Anton Katilin
37 * @author Vladimir Kondratyev
39 public class TabbedPaneWrapper
{
40 private static final PrevNextActionsDescriptor DEFAULT_SHORTCUTS
= new PrevNextActionsDescriptor(IdeActions
.ACTION_NEXT_TAB
, IdeActions
.ACTION_PREVIOUS_TAB
);
41 protected TabbedPane myTabbedPane
;
42 protected JComponent myTabbedPaneHolder
;
44 private TabFactory myFactory
;
46 protected TabbedPaneWrapper(boolean construct
) {
48 init(SwingConstants
.TOP
, DEFAULT_SHORTCUTS
, new JTabbedPaneFactory(this));
52 public TabbedPaneWrapper(@NotNull Disposable parentDisposable
) {
53 this(SwingConstants
.TOP
, DEFAULT_SHORTCUTS
, parentDisposable
);
57 * Creates tabbed pane wrapper with specified tab placement
59 * @param tabPlacement tab placement. It one of the <code>SwingConstants.TOP</code>,
60 * <code>SwingConstants.LEFT</code>, <code>SwingConstants.BOTTOM</code> or
61 * <code>SwingConstants.RIGHT</code>.
63 public TabbedPaneWrapper(int tabPlacement
, PrevNextActionsDescriptor installKeyboardNavigation
, @NotNull Disposable parentDisposable
) {
64 final TabFactory factory
;
65 if (SwingConstants
.BOTTOM
== tabPlacement
|| SwingConstants
.TOP
== tabPlacement
) {
66 factory
= new JBTabsFactory(this, null, parentDisposable
);
68 factory
= new JTabbedPaneFactory(this);
71 init(tabPlacement
, installKeyboardNavigation
, factory
);
74 void init(int tabPlacement
, PrevNextActionsDescriptor installKeyboardNavigation
, TabFactory tabbedPaneFactory
) {
75 myFactory
= tabbedPaneFactory
;
77 myTabbedPane
= createTabbedPane(tabPlacement
);
78 myTabbedPane
.putClientProperty(TabbedPaneWrapper
.class, myTabbedPane
);
79 myTabbedPane
.setKeyboardNavigation(installKeyboardNavigation
);
81 myTabbedPaneHolder
= createTabbedPaneHolder();
82 myTabbedPaneHolder
.add(myTabbedPane
.getComponent(), BorderLayout
.CENTER
);
83 myTabbedPaneHolder
.setFocusCycleRoot(true);
84 myTabbedPaneHolder
.setFocusTraversalPolicy(new _MyFocusTraversalPolicy());
86 assertIsDispatchThread();
89 public boolean isDisposed() {
90 return myTabbedPane
!= null && myTabbedPane
.isDisposed();
93 private void assertIsDispatchThread() {
94 final ApplicationEx application
= ApplicationManagerEx
.getApplicationEx();
95 if (application
!= null){
96 application
.assertIsDispatchThread(myTabbedPane
.getComponent());
100 public final void addChangeListener(final ChangeListener listener
){
101 assertIsDispatchThread();
102 myTabbedPane
.addChangeListener(listener
);
105 public final void removeChangeListener(final ChangeListener listener
){
106 assertIsDispatchThread();
107 myTabbedPane
.removeChangeListener(listener
);
110 protected TabbedPaneHolder
createTabbedPaneHolder() {
111 return myFactory
.createTabbedPaneHolder();
114 public final JComponent
getComponent() {
115 assertIsDispatchThread();
116 return myTabbedPaneHolder
;
120 * @see javax.swing.JTabbedPane#addTab(java.lang.String, javax.swing.Icon, java.awt.Component, java.lang.String)
122 public final synchronized void addTab(final String title
, final Icon icon
, final JComponent component
, final String tip
) {
123 insertTab(title
, icon
, component
, tip
, myTabbedPane
.getTabCount());
126 public final synchronized void addTab(final String title
, final JComponent component
) {
127 insertTab(title
, null, component
, null, myTabbedPane
.getTabCount());
130 public synchronized void insertTab(final String title
, final Icon icon
, final JComponent component
, final String tip
, final int index
) {
131 myTabbedPane
.insertTab(title
, icon
, createTabWrapper(component
), tip
, index
);
134 protected TabWrapper
createTabWrapper(JComponent component
) {
135 return myFactory
.createTabWrapper(component
);
138 protected TabbedPane
createTabbedPane(final int tabPlacement
) {
139 return myFactory
.createTabbedPane(tabPlacement
);
143 * @see javax.swing.JTabbedPane#setTabPlacement
145 public final void setTabPlacement(final int tabPlacement
) {
146 assertIsDispatchThread();
147 myTabbedPane
.setTabPlacement(tabPlacement
);
150 public final void addMouseListener(final MouseListener listener
) {
151 assertIsDispatchThread();
152 myTabbedPane
.addMouseListener(listener
);
155 public final synchronized int getSelectedIndex() {
156 return myTabbedPane
.getSelectedIndex();
160 * @see javax.swing.JTabbedPane#getSelectedComponent()
162 public final synchronized JComponent
getSelectedComponent() {
163 // Workaround for JDK 6 bug
164 final TabWrapper tabWrapper
= myTabbedPane
.getTabCount() > 0 ?
(TabWrapper
)myTabbedPane
.getSelectedComponent():null;
165 return tabWrapper
!= null ? tabWrapper
.getComponent() : null;
168 public final void setSelectedIndex(final int index
) {
169 setSelectedIndex(index
, true);
172 public final void setSelectedIndex(final int index
, boolean requestFocus
) {
173 assertIsDispatchThread();
175 final boolean hadFocus
= IJSwingUtilities
.hasFocus2(myTabbedPaneHolder
);
176 myTabbedPane
.setSelectedIndex(index
);
177 if (hadFocus
&& requestFocus
) {
178 myTabbedPaneHolder
.requestFocus();
182 public final void setSelectedComponent(final JComponent component
){
183 assertIsDispatchThread();
185 final int index
=indexOfComponent(component
);
187 throw new IllegalArgumentException("component not found in tabbed pane wrapper");
189 setSelectedIndex(index
);
192 public final synchronized void removeTabAt(final int index
) {
193 assertIsDispatchThread();
195 final boolean hadFocus
= IJSwingUtilities
.hasFocus2(myTabbedPaneHolder
);
196 final TabWrapper wrapper
= getWrapperAt(index
);
198 myTabbedPane
.removeTabAt(index
);
199 if (myTabbedPane
.getTabCount() == 0) {
200 // to clear BasicTabbedPaneUI.visibleComponent field
201 myTabbedPane
.revalidate();
204 myTabbedPaneHolder
.requestFocus();
212 public final synchronized int getTabCount() {
213 return myTabbedPane
.getTabCount();
216 public final Color
getForegroundAt(final int index
){
217 assertIsDispatchThread();
218 return myTabbedPane
.getForegroundAt(index
);
222 * @see javax.swing.JTabbedPane#setForegroundAt(int, java.awt.Color)
224 public final void setForegroundAt(final int index
,final Color color
){
225 assertIsDispatchThread();
226 myTabbedPane
.setForegroundAt(index
,color
);
230 * @see javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)
232 public final synchronized JComponent
getComponentAt(final int i
) {
233 return getWrapperAt(i
).getComponent();
236 private TabWrapper
getWrapperAt(final int i
) {
237 return (TabWrapper
)myTabbedPane
.getComponentAt(i
);
240 public final void setTitleAt(final int index
, final String title
) {
241 assertIsDispatchThread();
242 myTabbedPane
.setTitleAt(index
, title
);
245 public final void setToolTipTextAt(final int index
, final String toolTipText
) {
246 assertIsDispatchThread();
247 myTabbedPane
.setToolTipTextAt(index
, toolTipText
);
251 * @see javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component)
253 public final synchronized void setComponentAt(final int index
, final JComponent component
) {
254 assertIsDispatchThread();
255 myTabbedPane
.setComponentAt(index
, createTabWrapper(component
));
259 * @see javax.swing.JTabbedPane#setIconAt(int, javax.swing.Icon)
261 public final void setIconAt(final int index
, final Icon icon
) {
262 assertIsDispatchThread();
263 myTabbedPane
.setIconAt(index
, icon
);
266 public final void setEnabledAt(final int index
, final boolean enabled
) {
267 assertIsDispatchThread();
268 myTabbedPane
.setEnabledAt(index
, enabled
);
272 * @see javax.swing.JTabbedPane#indexOfComponent(java.awt.Component)
274 public final synchronized int indexOfComponent(final JComponent component
) {
275 for (int i
=0; i
< myTabbedPane
.getTabCount(); i
++) {
276 final JComponent c
= getWrapperAt(i
).getComponent();
277 if (c
== component
) {
285 * @see javax.swing.JTabbedPane#getTabLayoutPolicy
287 public final synchronized int getTabLayoutPolicy(){
288 return myTabbedPane
.getTabLayoutPolicy();
292 * @see javax.swing.JTabbedPane#setTabLayoutPolicy
294 public final synchronized void setTabLayoutPolicy(final int policy
){
295 myTabbedPane
.setTabLayoutPolicy(policy
);
296 final int index
=myTabbedPane
.getSelectedIndex();
298 myTabbedPane
.scrollTabToVisible(index
);
303 * @deprecated Keyboard navigation is installed/deinstalled automatically. This method does nothing now.
305 public final void installKeyboardNavigation(){
309 * @deprecated Keyboard navigation is installed/deinstalled automatically. This method does nothing now.
311 public final void uninstallKeyboardNavigation(){
314 public final String
getTitleAt(final int i
) {
315 return myTabbedPane
.getTitleAt(i
);
318 public void setSelectedTitle(@Nullable final String title
) {
319 if (title
== null) return;
321 for (int i
= 0; i
< myTabbedPane
.getTabCount(); i
++) {
322 final String each
= myTabbedPane
.getTitleAt(i
);
323 if (title
.equals(each
)) {
324 myTabbedPane
.setSelectedIndex(i
);
331 public String
getSelectedTitle() {
332 return getSelectedIndex() < 0 ?
null : getTitleAt(getSelectedIndex());
335 public void removeAll() {
336 myTabbedPane
.removeAll();
339 public static final class TabWrapper
extends JPanel
implements DataProvider
{
340 private JComponent myComponent
;
342 boolean myCustomFocus
= true;
344 public TabWrapper(@NotNull final JComponent component
) {
345 super(new BorderLayout());
346 myComponent
= component
;
347 add(component
, BorderLayout
.CENTER
);
351 * Make possible to search down for DataProviders
353 public Object
getData(final String dataId
) {
354 if(myComponent
instanceof DataProvider
){
355 return ((DataProvider
)myComponent
).getData(dataId
);
361 public JComponent
getComponent() {
366 * TabWrappers are never reused so we can fix the leak in some LAF's TabbedPane UI by cleanuping ourselves.
368 public void dispose() {
369 if (myComponent
!= null) {
375 public boolean requestDefaultFocus() {
376 if (!myCustomFocus
) return super.requestDefaultFocus();
377 if (myComponent
== null) return false; // Just in case someone requests the focus when we're already removed from the Swing tree.
378 final JComponent preferredFocusedComponent
= IdeFocusTraversalPolicy
.getPreferredFocusedComponent(myComponent
);
379 if (preferredFocusedComponent
!= null) {
380 if (!preferredFocusedComponent
.requestFocusInWindow()) {
381 preferredFocusedComponent
.requestFocus();
385 return myComponent
.requestDefaultFocus();
389 public void requestFocus() {
390 if (!myCustomFocus
) {
391 super.requestFocus();
393 requestDefaultFocus();
397 public boolean requestFocusInWindow() {
398 if (!myCustomFocus
) return super.requestFocusInWindow();
399 return requestDefaultFocus();
403 private final class _MyFocusTraversalPolicy
extends IdeFocusTraversalPolicy
{
404 public final Component
getDefaultComponentImpl(final Container focusCycleRoot
) {
405 final JComponent component
=getSelectedComponent();
407 return IdeFocusTraversalPolicy
.getPreferredFocusedComponent(component
, this);
414 protected static class TabbedPaneHolder
extends JPanel
{
416 private final TabbedPaneWrapper myWrapper
;
418 protected TabbedPaneHolder(TabbedPaneWrapper wrapper
) {
419 super(new BorderLayout());
423 public boolean requestDefaultFocus() {
424 final JComponent preferredFocusedComponent
= IdeFocusTraversalPolicy
.getPreferredFocusedComponent(myWrapper
.myTabbedPane
.getComponent());
425 if (preferredFocusedComponent
!= null) {
426 if (!preferredFocusedComponent
.requestFocusInWindow()) {
427 preferredFocusedComponent
.requestFocus();
431 return super.requestDefaultFocus();
435 public final void requestFocus() {
436 requestDefaultFocus();
439 public final boolean requestFocusInWindow() {
440 return requestDefaultFocus();
443 public void updateUI() {
445 if (myWrapper
!= null) {
446 myWrapper
.myTabbedPane
.updateUI();
451 public static TabbedPaneWrapper
get(JTabbedPane tabs
) {
452 return (TabbedPaneWrapper
)tabs
.getClientProperty(TabbedPaneWrapper
.class);
455 private interface TabFactory
{
456 TabbedPane
createTabbedPane(int tabPlacement
);
457 TabbedPaneHolder
createTabbedPaneHolder();
458 TabWrapper
createTabWrapper(JComponent component
);
461 private static class JTabbedPaneFactory
implements TabFactory
{
462 private final TabbedPaneWrapper myWrapper
;
464 private JTabbedPaneFactory(TabbedPaneWrapper wrapper
) {
468 public TabbedPane
createTabbedPane(int tabPlacement
) {
469 return new TabbedPaneImpl(tabPlacement
);
472 public TabbedPaneHolder
createTabbedPaneHolder() {
473 return new TabbedPaneHolder(myWrapper
);
476 public TabWrapper
createTabWrapper(JComponent component
) {
477 return new TabWrapper(component
);
481 private static class JBTabsFactory
implements TabFactory
{
483 private final Project myProject
;
484 private final Disposable myParent
;
485 private final TabbedPaneWrapper myWrapper
;
487 private JBTabsFactory(TabbedPaneWrapper wrapper
, Project project
, @NotNull Disposable parent
) {
493 public TabbedPane
createTabbedPane(int tabPlacement
) {
494 return new JBTabsPaneImpl(myProject
, tabPlacement
, myParent
);
497 public TabbedPaneHolder
createTabbedPaneHolder() {
498 return new TabbedPaneHolder(myWrapper
) {
500 public boolean requestDefaultFocus() {
501 getTabs().requestFocus();
508 public TabWrapper
createTabWrapper(JComponent component
) {
509 final TabWrapper tabWrapper
= new TabWrapper(component
);
510 tabWrapper
.myCustomFocus
= false;
514 public JBTabs
getTabs() {
515 return ((JBTabsPaneImpl
)myWrapper
.myTabbedPane
).getTabs();
518 public void dispose() {
522 public static class AsJBTabs
extends TabbedPaneWrapper
{
523 public AsJBTabs(@Nullable Project project
, int tabPlacement
, PrevNextActionsDescriptor installKeyboardNavigation
, @NotNull Disposable parent
) {
525 init(tabPlacement
, installKeyboardNavigation
, new JBTabsFactory(this, project
, parent
));
528 public JBTabs
getTabs() {
529 return ((JBTabsPaneImpl
)myTabbedPane
).getTabs();
533 public static class AsJTabbedPane
extends TabbedPaneWrapper
{
534 public AsJTabbedPane(int tabPlacement
) {
536 init(tabPlacement
, DEFAULT_SHORTCUTS
, new JTabbedPaneFactory(this));