| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.ui; |
| |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.DataProvider; |
| import com.intellij.openapi.application.ex.ApplicationEx; |
| import com.intellij.openapi.application.ex.ApplicationManagerEx; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; |
| import com.intellij.ui.tabs.JBTabs; |
| import com.intellij.util.IJSwingUtilities; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.ChangeListener; |
| import java.awt.*; |
| import java.awt.event.MouseListener; |
| |
| /** |
| * @author Anton Katilin |
| * @author Vladimir Kondratyev |
| */ |
| public class TabbedPaneWrapper { |
| protected TabbedPane myTabbedPane; |
| protected JComponent myTabbedPaneHolder; |
| |
| private TabFactory myFactory; |
| |
| protected TabbedPaneWrapper(boolean construct) { |
| if (construct) { |
| init(SwingConstants.TOP, TabbedPaneImpl.DEFAULT_PREV_NEXT_SHORTCUTS, new JTabbedPaneFactory(this)); |
| } |
| } |
| |
| public TabbedPaneWrapper(@NotNull Disposable parentDisposable) { |
| this(SwingConstants.TOP, TabbedPaneImpl.DEFAULT_PREV_NEXT_SHORTCUTS, parentDisposable); |
| } |
| |
| /** |
| * Creates tabbed pane wrapper with specified tab placement |
| * |
| * @param tabPlacement tab placement. It one of the <code>SwingConstants.TOP</code>, |
| * <code>SwingConstants.LEFT</code>, <code>SwingConstants.BOTTOM</code> or |
| * <code>SwingConstants.RIGHT</code>. |
| */ |
| public TabbedPaneWrapper(int tabPlacement, PrevNextActionsDescriptor installKeyboardNavigation, @NotNull Disposable parentDisposable) { |
| final TabFactory factory; |
| if (SwingConstants.BOTTOM == tabPlacement || SwingConstants.TOP == tabPlacement) { |
| factory = new JBTabsFactory(this, null, parentDisposable); |
| } else { |
| factory = new JTabbedPaneFactory(this); |
| } |
| |
| init(tabPlacement, installKeyboardNavigation, factory); |
| } |
| |
| void init(int tabPlacement, PrevNextActionsDescriptor installKeyboardNavigation, TabFactory tabbedPaneFactory) { |
| myFactory = tabbedPaneFactory; |
| |
| myTabbedPane = createTabbedPane(tabPlacement); |
| myTabbedPane.putClientProperty(TabbedPaneWrapper.class, myTabbedPane); |
| myTabbedPane.setKeyboardNavigation(installKeyboardNavigation); |
| |
| myTabbedPaneHolder = createTabbedPaneHolder(); |
| myTabbedPaneHolder.add(myTabbedPane.getComponent(), BorderLayout.CENTER); |
| myTabbedPaneHolder.setFocusCycleRoot(true); |
| myTabbedPaneHolder.setFocusTraversalPolicy(new _MyFocusTraversalPolicy()); |
| |
| assertIsDispatchThread(); |
| } |
| |
| public boolean isDisposed() { |
| return myTabbedPane != null && myTabbedPane.isDisposed(); |
| } |
| |
| private void assertIsDispatchThread() { |
| final ApplicationEx application = ApplicationManagerEx.getApplicationEx(); |
| if (application != null){ |
| application.assertIsDispatchThread(myTabbedPane.getComponent()); |
| } |
| } |
| |
| public final void addChangeListener(final ChangeListener listener){ |
| assertIsDispatchThread(); |
| myTabbedPane.addChangeListener(listener); |
| } |
| |
| public final void removeChangeListener(final ChangeListener listener){ |
| assertIsDispatchThread(); |
| myTabbedPane.removeChangeListener(listener); |
| } |
| |
| protected TabbedPaneHolder createTabbedPaneHolder() { |
| return myFactory.createTabbedPaneHolder(); |
| } |
| |
| public final JComponent getComponent() { |
| assertIsDispatchThread(); |
| return myTabbedPaneHolder; |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#addTab(java.lang.String, javax.swing.Icon, java.awt.Component, java.lang.String) |
| */ |
| public final synchronized void addTab(final String title, final Icon icon, final JComponent component, final String tip) { |
| insertTab(title, icon, component, tip, myTabbedPane.getTabCount()); |
| } |
| |
| public final synchronized void addTab(final String title, final JComponent component) { |
| insertTab(title, null, component, null, myTabbedPane.getTabCount()); |
| } |
| |
| public synchronized void insertTab(final String title, final Icon icon, final JComponent component, final String tip, final int index) { |
| myTabbedPane.insertTab(title, icon, createTabWrapper(component), tip, index); |
| } |
| |
| protected TabWrapper createTabWrapper(JComponent component) { |
| return myFactory.createTabWrapper(component); |
| } |
| |
| protected TabbedPane createTabbedPane(final int tabPlacement) { |
| return myFactory.createTabbedPane(tabPlacement); |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#setTabPlacement |
| */ |
| public final void setTabPlacement(final int tabPlacement) { |
| assertIsDispatchThread(); |
| myTabbedPane.setTabPlacement(tabPlacement); |
| } |
| |
| public final void addMouseListener(final MouseListener listener) { |
| assertIsDispatchThread(); |
| myTabbedPane.addMouseListener(listener); |
| } |
| |
| public final synchronized int getSelectedIndex() { |
| return myTabbedPane.getSelectedIndex(); |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#getSelectedComponent() |
| */ |
| public final synchronized JComponent getSelectedComponent() { |
| // Workaround for JDK 6 bug |
| final TabWrapper tabWrapper = myTabbedPane.getTabCount() > 0 ? (TabWrapper)myTabbedPane.getSelectedComponent():null; |
| return tabWrapper != null ? tabWrapper.getComponent() : null; |
| } |
| |
| public final void setSelectedIndex(final int index) { |
| setSelectedIndex(index, true); |
| } |
| |
| public final void setSelectedIndex(final int index, boolean requestFocus) { |
| assertIsDispatchThread(); |
| |
| final boolean hadFocus = IJSwingUtilities.hasFocus2(myTabbedPaneHolder); |
| myTabbedPane.setSelectedIndex(index); |
| if (hadFocus && requestFocus) { |
| myTabbedPaneHolder.requestFocus(); |
| } |
| } |
| |
| public final void setSelectedComponent(final JComponent component){ |
| assertIsDispatchThread(); |
| |
| final int index=indexOfComponent(component); |
| if(index==-1){ |
| throw new IllegalArgumentException("component not found in tabbed pane wrapper"); |
| } |
| setSelectedIndex(index); |
| } |
| |
| public final synchronized void removeTabAt(final int index) { |
| assertIsDispatchThread(); |
| |
| final boolean hadFocus = IJSwingUtilities.hasFocus2(myTabbedPaneHolder); |
| final TabWrapper wrapper = getWrapperAt(index); |
| try { |
| myTabbedPane.removeTabAt(index); |
| if (myTabbedPane.getTabCount() == 0) { |
| // to clear BasicTabbedPaneUI.visibleComponent field |
| myTabbedPane.revalidate(); |
| } |
| if (hadFocus) { |
| myTabbedPaneHolder.requestFocus(); |
| } |
| } |
| finally { |
| wrapper.dispose(); |
| } |
| } |
| |
| public final synchronized int getTabCount() { |
| return myTabbedPane.getTabCount(); |
| } |
| |
| public final Color getForegroundAt(final int index){ |
| assertIsDispatchThread(); |
| return myTabbedPane.getForegroundAt(index); |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#setForegroundAt(int, java.awt.Color) |
| */ |
| public final void setForegroundAt(final int index,final Color color){ |
| assertIsDispatchThread(); |
| myTabbedPane.setForegroundAt(index,color); |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component) |
| */ |
| public final synchronized JComponent getComponentAt(final int i) { |
| return getWrapperAt(i).getComponent(); |
| } |
| |
| private TabWrapper getWrapperAt(final int i) { |
| return (TabWrapper)myTabbedPane.getComponentAt(i); |
| } |
| |
| public final void setTitleAt(final int index, final String title) { |
| assertIsDispatchThread(); |
| myTabbedPane.setTitleAt(index, title); |
| } |
| |
| public final void setToolTipTextAt(final int index, final String toolTipText) { |
| assertIsDispatchThread(); |
| myTabbedPane.setToolTipTextAt(index, toolTipText); |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#setComponentAt(int, java.awt.Component) |
| */ |
| public final synchronized void setComponentAt(final int index, final JComponent component) { |
| assertIsDispatchThread(); |
| myTabbedPane.setComponentAt(index, createTabWrapper(component)); |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#setIconAt(int, javax.swing.Icon) |
| */ |
| public final void setIconAt(final int index, final Icon icon) { |
| assertIsDispatchThread(); |
| myTabbedPane.setIconAt(index, icon); |
| } |
| |
| public final void setEnabledAt(final int index, final boolean enabled) { |
| assertIsDispatchThread(); |
| myTabbedPane.setEnabledAt(index, enabled); |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#indexOfComponent(java.awt.Component) |
| */ |
| public final synchronized int indexOfComponent(final JComponent component) { |
| for (int i=0; i < myTabbedPane.getTabCount(); i++) { |
| final JComponent c = getWrapperAt(i).getComponent(); |
| if (c == component) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#getTabLayoutPolicy |
| */ |
| public final synchronized int getTabLayoutPolicy(){ |
| return myTabbedPane.getTabLayoutPolicy(); |
| } |
| |
| /** |
| * @see javax.swing.JTabbedPane#setTabLayoutPolicy |
| */ |
| public final synchronized void setTabLayoutPolicy(final int policy){ |
| myTabbedPane.setTabLayoutPolicy(policy); |
| final int index=myTabbedPane.getSelectedIndex(); |
| if(index!=-1){ |
| myTabbedPane.scrollTabToVisible(index); |
| } |
| } |
| |
| /** |
| * @deprecated Keyboard navigation is installed/deinstalled automatically. This method does nothing now. |
| */ |
| public final void installKeyboardNavigation(){ |
| } |
| |
| /** |
| * @deprecated Keyboard navigation is installed/deinstalled automatically. This method does nothing now. |
| */ |
| public final void uninstallKeyboardNavigation(){ |
| } |
| |
| public final String getTitleAt(final int i) { |
| return myTabbedPane.getTitleAt(i); |
| } |
| |
| public void setSelectedTitle(@Nullable final String title) { |
| if (title == null) return; |
| |
| for (int i = 0; i < myTabbedPane.getTabCount(); i++) { |
| final String each = myTabbedPane.getTitleAt(i); |
| if (title.equals(each)) { |
| myTabbedPane.setSelectedIndex(i); |
| break; |
| } |
| } |
| } |
| |
| @Nullable |
| public String getSelectedTitle() { |
| return getSelectedIndex() < 0 ? null : getTitleAt(getSelectedIndex()); |
| } |
| |
| public void removeAll() { |
| myTabbedPane.removeAll(); |
| } |
| |
| public static final class TabWrapper extends JPanel implements DataProvider{ |
| private JComponent myComponent; |
| |
| boolean myCustomFocus = true; |
| |
| public TabWrapper(@NotNull final JComponent component) { |
| super(new BorderLayout()); |
| myComponent = component; |
| add(component, BorderLayout.CENTER); |
| } |
| |
| /* |
| * Make possible to search down for DataProviders |
| */ |
| public Object getData(final String dataId) { |
| if(myComponent instanceof DataProvider){ |
| return ((DataProvider)myComponent).getData(dataId); |
| } else { |
| return null; |
| } |
| } |
| |
| public JComponent getComponent() { |
| return myComponent; |
| } |
| |
| /** |
| * TabWrappers are never reused so we can fix the leak in some LAF's TabbedPane UI by cleanuping ourselves. |
| */ |
| public void dispose() { |
| if (myComponent != null) { |
| remove(myComponent); |
| myComponent = null; |
| } |
| } |
| |
| public boolean requestDefaultFocus() { |
| if (!myCustomFocus) return super.requestDefaultFocus(); |
| if (myComponent == null) return false; // Just in case someone requests the focus when we're already removed from the Swing tree. |
| final JComponent preferredFocusedComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent(myComponent); |
| if (preferredFocusedComponent != null) { |
| if (!preferredFocusedComponent.requestFocusInWindow()) { |
| preferredFocusedComponent.requestFocus(); |
| } |
| return true; |
| } else { |
| return myComponent.requestDefaultFocus(); |
| } |
| } |
| |
| public void requestFocus() { |
| if (!myCustomFocus) { |
| super.requestFocus(); |
| } else { |
| requestDefaultFocus(); |
| } |
| } |
| |
| public boolean requestFocusInWindow() { |
| if (!myCustomFocus) return super.requestFocusInWindow(); |
| return requestDefaultFocus(); |
| } |
| } |
| |
| private final class _MyFocusTraversalPolicy extends IdeFocusTraversalPolicy{ |
| public final Component getDefaultComponentImpl(final Container focusCycleRoot) { |
| final JComponent component=getSelectedComponent(); |
| if(component!=null){ |
| return IdeFocusTraversalPolicy.getPreferredFocusedComponent(component, this); |
| }else{ |
| return null; |
| } |
| } |
| } |
| |
| protected static class TabbedPaneHolder extends JPanel { |
| |
| private final TabbedPaneWrapper myWrapper; |
| |
| protected TabbedPaneHolder(TabbedPaneWrapper wrapper) { |
| super(new BorderLayout()); |
| myWrapper = wrapper; |
| } |
| |
| public boolean requestDefaultFocus() { |
| final JComponent preferredFocusedComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent(myWrapper.myTabbedPane.getComponent()); |
| if (preferredFocusedComponent != null) { |
| if (!preferredFocusedComponent.requestFocusInWindow()) { |
| preferredFocusedComponent.requestFocus(); |
| } |
| return true; |
| } else { |
| return super.requestDefaultFocus(); |
| } |
| } |
| |
| public final void requestFocus() { |
| requestDefaultFocus(); |
| } |
| |
| public final boolean requestFocusInWindow() { |
| return requestDefaultFocus(); |
| } |
| |
| public void updateUI() { |
| super.updateUI(); |
| if (myWrapper != null) { |
| myWrapper.myTabbedPane.updateUI(); |
| } |
| } |
| } |
| |
| public static TabbedPaneWrapper get(JTabbedPane tabs) { |
| return (TabbedPaneWrapper)tabs.getClientProperty(TabbedPaneWrapper.class); |
| } |
| |
| private interface TabFactory { |
| TabbedPane createTabbedPane(int tabPlacement); |
| TabbedPaneHolder createTabbedPaneHolder(); |
| TabWrapper createTabWrapper(JComponent component); |
| } |
| |
| private static class JTabbedPaneFactory implements TabFactory { |
| private final TabbedPaneWrapper myWrapper; |
| |
| private JTabbedPaneFactory(TabbedPaneWrapper wrapper) { |
| myWrapper = wrapper; |
| } |
| |
| public TabbedPane createTabbedPane(int tabPlacement) { |
| return new TabbedPaneImpl(tabPlacement); |
| } |
| |
| public TabbedPaneHolder createTabbedPaneHolder() { |
| return new TabbedPaneHolder(myWrapper); |
| } |
| |
| public TabWrapper createTabWrapper(JComponent component) { |
| return new TabWrapper(component); |
| } |
| } |
| |
| private static class JBTabsFactory implements TabFactory { |
| |
| private final Project myProject; |
| private final Disposable myParent; |
| private final TabbedPaneWrapper myWrapper; |
| |
| private JBTabsFactory(TabbedPaneWrapper wrapper, Project project, @NotNull Disposable parent) { |
| myWrapper = wrapper; |
| myProject = project; |
| myParent = parent; |
| } |
| |
| public TabbedPane createTabbedPane(int tabPlacement) { |
| return new JBTabsPaneImpl(myProject, tabPlacement, myParent); |
| } |
| |
| public TabbedPaneHolder createTabbedPaneHolder() { |
| return new TabbedPaneHolder(myWrapper) { |
| @Override |
| public boolean requestDefaultFocus() { |
| getTabs().requestFocus(); |
| return true; |
| } |
| |
| }; |
| } |
| |
| public TabWrapper createTabWrapper(JComponent component) { |
| final TabWrapper tabWrapper = new TabWrapper(component); |
| tabWrapper.myCustomFocus = false; |
| return tabWrapper; |
| } |
| |
| public JBTabs getTabs() { |
| return ((JBTabsPaneImpl)myWrapper.myTabbedPane).getTabs(); |
| } |
| |
| public void dispose() { |
| } |
| } |
| |
| public static class AsJBTabs extends TabbedPaneWrapper { |
| public AsJBTabs(@Nullable Project project, int tabPlacement, PrevNextActionsDescriptor installKeyboardNavigation, @NotNull Disposable parent) { |
| super(false); |
| init(tabPlacement, installKeyboardNavigation, new JBTabsFactory(this, project, parent)); |
| } |
| |
| public JBTabs getTabs() { |
| return ((JBTabsPaneImpl)myTabbedPane).getTabs(); |
| } |
| } |
| |
| public static class AsJTabbedPane extends TabbedPaneWrapper { |
| public AsJTabbedPane(int tabPlacement) { |
| super(false); |
| init(tabPlacement, TabbedPaneImpl.DEFAULT_PREV_NEXT_SHORTCUTS, new JTabbedPaneFactory(this)); |
| } |
| } |
| |
| } |