blob: 80f8ecd461ddb9cb5ac3f23188da0095ca8e17ff [file] [log] [blame]
/*
* 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.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.components.JBTabbedPane;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.NonNls;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.TabbedPaneUI;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class TabbedPaneImpl extends JBTabbedPane implements TabbedPane {
public static final PrevNextActionsDescriptor DEFAULT_PREV_NEXT_SHORTCUTS = new PrevNextActionsDescriptor(IdeActions.ACTION_NEXT_TAB,
IdeActions.ACTION_PREVIOUS_TAB);
private static final Logger LOG = Logger.getInstance("#com.intellij.ui.TabbedPaneImpl");
private ScrollableTabSupport myScrollableTabSupport;
private AnAction myNextTabAction = null;
private AnAction myPreviousTabAction = null;
public PrevNextActionsDescriptor myInstallKeyboardNavigation = null;
public TabbedPaneImpl(@JdkConstants.TabPlacement int tabPlacement) {
super(tabPlacement);
setFocusable(false);
addMouseListener(
new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
_requestDefaultFocus();
}
}
);
}
@Override
public void setKeyboardNavigation(PrevNextActionsDescriptor installKeyboardNavigation) {
myInstallKeyboardNavigation = installKeyboardNavigation;
}
@Override
public JComponent getComponent() {
return this;
}
@Override
public void addNotify() {
super.addNotify();
if (myInstallKeyboardNavigation != null) {
installKeyboardNavigation(myInstallKeyboardNavigation);
}
}
@Override
public void removeNotify() {
super.removeNotify();
if (myInstallKeyboardNavigation != null) {
uninstallKeyboardNavigation();
}
}
@SuppressWarnings({"NonStaticInitializer"})
private void installKeyboardNavigation(final PrevNextActionsDescriptor installKeyboardNavigation){
myNextTabAction = new AnAction() {
{
setEnabledInModalContext(true);
}
@Override
public void actionPerformed(final AnActionEvent e) {
int index = getSelectedIndex() + 1;
if (index >= getTabCount()) {
index = 0;
}
setSelectedIndex(index);
}
};
final AnAction nextAction = ActionManager.getInstance().getAction(installKeyboardNavigation.getNextActionId());
LOG.assertTrue(nextAction != null, "Cannot find action with specified id: " + installKeyboardNavigation.getNextActionId());
myNextTabAction.registerCustomShortcutSet(nextAction.getShortcutSet(), this);
myPreviousTabAction = new AnAction() {
{
setEnabledInModalContext(true);
}
@Override
public void actionPerformed(final AnActionEvent e) {
int index = getSelectedIndex() - 1;
if (index < 0) {
index = getTabCount() - 1;
}
setSelectedIndex(index);
}
};
final AnAction prevAction = ActionManager.getInstance().getAction(installKeyboardNavigation.getPrevActionId());
LOG.assertTrue(prevAction != null, "Cannot find action with specified id: " + installKeyboardNavigation.getPrevActionId());
myPreviousTabAction.registerCustomShortcutSet(prevAction.getShortcutSet(), this);
}
private void uninstallKeyboardNavigation() {
if (myNextTabAction != null) {
myNextTabAction.unregisterCustomShortcutSet(this);
myNextTabAction = null;
}
if (myPreviousTabAction != null) {
myPreviousTabAction.unregisterCustomShortcutSet(this);
myPreviousTabAction = null;
}
}
@Override
public void setUI(final TabbedPaneUI ui){
super.setUI(ui);
if(ui instanceof BasicTabbedPaneUI){
myScrollableTabSupport=new ScrollableTabSupport((BasicTabbedPaneUI)ui);
}else{
myScrollableTabSupport=null;
}
}
/**
* Scrolls tab to visible area. If tabbed pane has <code>JTabbedPane.WRAP_TAB_LAYOUT</code> layout policy then
* the method does nothing.
* @param index index of tab to be scrolled.
*/
@Override
public final void scrollTabToVisible(final int index){
if(myScrollableTabSupport==null|| WRAP_TAB_LAYOUT==getTabLayoutPolicy()){ // tab scrolling isn't supported by UI
return;
}
final TabbedPaneUI tabbedPaneUI=getUI();
Rectangle tabBounds=tabbedPaneUI.getTabBounds(this,index);
final int tabPlacement=getTabPlacement();
if(TOP==tabPlacement || BOTTOM==tabPlacement){ //tabs are on the top or bottom
if(tabBounds.x<50){ //if tab is to the left of visible area
int leadingTabIndex=myScrollableTabSupport.getLeadingTabIndex();
while(leadingTabIndex != index && leadingTabIndex>0 && tabBounds.x<50){
myScrollableTabSupport.setLeadingTabIndex(leadingTabIndex-1);
leadingTabIndex=myScrollableTabSupport.getLeadingTabIndex();
tabBounds=tabbedPaneUI.getTabBounds(this,index);
}
}else if(tabBounds.x+tabBounds.width>getWidth()-50){ // if tab's right side is out of visible range
int leadingTabIndex=myScrollableTabSupport.getLeadingTabIndex();
while(leadingTabIndex != index && leadingTabIndex<getTabCount()-1 && tabBounds.x+tabBounds.width>getWidth()-50){
myScrollableTabSupport.setLeadingTabIndex(leadingTabIndex+1);
leadingTabIndex=myScrollableTabSupport.getLeadingTabIndex();
tabBounds=tabbedPaneUI.getTabBounds(this,index);
}
}
}else{ // tabs are on left or right side
if(tabBounds.y<30){ //tab is above visible area
int leadingTabIndex=myScrollableTabSupport.getLeadingTabIndex();
while(leadingTabIndex != index && leadingTabIndex>0 && tabBounds.y<30){
myScrollableTabSupport.setLeadingTabIndex(leadingTabIndex-1);
leadingTabIndex=myScrollableTabSupport.getLeadingTabIndex();
tabBounds=tabbedPaneUI.getTabBounds(this,index);
}
} else if(tabBounds.y+tabBounds.height>getHeight()-30){ //tab is under visible area
int leadingTabIndex=myScrollableTabSupport.getLeadingTabIndex();
while(leadingTabIndex != index && leadingTabIndex<getTabCount()-1 && tabBounds.y+tabBounds.height>getHeight()-30){
myScrollableTabSupport.setLeadingTabIndex(leadingTabIndex+1);
leadingTabIndex=myScrollableTabSupport.getLeadingTabIndex();
tabBounds=tabbedPaneUI.getTabBounds(this,index);
}
}
}
}
@Override
public void setSelectedIndex(final int index){
if (index >= getTabCount()) return;
try {
super.setSelectedIndex(index);
}
catch (ArrayIndexOutOfBoundsException e) {
//http://www.jetbrains.net/jira/browse/IDEADEV-22331
//may happen on Mac OSX 10.5
return;
}
scrollTabToVisible(index);
doLayout();
}
//http://www.jetbrains.net/jira/browse/IDEADEV-22331
//to let repaint happen since AIOBE is thrown from Mac OSX's UI
@Override
protected void fireStateChanged() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ChangeListener.class) {
// Lazily create the event:
if (changeEvent == null) changeEvent = new ChangeEvent(this);
final ChangeListener each = (ChangeListener)listeners[i + 1];
if (each != null && each.getClass().getName().indexOf("apple.laf.CUIAquaTabbedPane") >= 0) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
revalidate();
repaint();
}
});
continue;
}
each.stateChanged(changeEvent);
}
}
}
@Override
public final void removeTabAt (final int index) {
super.removeTabAt (index);
//This event should be fired necessarily because when swing fires an event
// page to be removed is still in the tabbed pane. There can be a situation when
// event fired according to swing event contains invalid information about selected page.
fireStateChanged();
}
private void _requestDefaultFocus() {
final Component selectedComponent = getSelectedComponent();
if (selectedComponent instanceof TabbedPaneWrapper.TabWrapper) {
((TabbedPaneWrapper.TabWrapper)selectedComponent).requestDefaultFocus();
}
else {
super.requestDefaultFocus();
}
}
protected final int getTabIndexAt(final int x,final int y){
final TabbedPaneUI ui=getUI();
for (int i = 0; i < getTabCount(); i++) {
final Rectangle bounds = ui.getTabBounds(this, i);
if (bounds.contains(x, y)) {
return i;
}
}
return -1;
}
/**
* That is hack-helper for working with scrollable tab layout. The problem is BasicTabbedPaneUI doesn't
* have any API to scroll tab to visible area. Therefore we have to implement it...
*/
private final class ScrollableTabSupport{
private final BasicTabbedPaneUI myUI;
@NonNls public static final String TAB_SCROLLER_NAME = "tabScroller";
@NonNls public static final String LEADING_TAB_INDEX_NAME = "leadingTabIndex";
@NonNls public static final String SET_LEADING_TAB_INDEX_METHOD = "setLeadingTabIndex";
public ScrollableTabSupport(final BasicTabbedPaneUI ui){
myUI=ui;
}
/**
* @return value of <code>leadingTabIndex</code> field of BasicTabbedPaneUI.ScrollableTabSupport class.
*/
public int getLeadingTabIndex() {
try {
final Field tabScrollerField = BasicTabbedPaneUI.class.getDeclaredField(TAB_SCROLLER_NAME);
tabScrollerField.setAccessible(true);
final Object tabScrollerValue = tabScrollerField.get(myUI);
final Field leadingTabIndexField = tabScrollerValue.getClass().getDeclaredField(LEADING_TAB_INDEX_NAME);
leadingTabIndexField.setAccessible(true);
return leadingTabIndexField.getInt(tabScrollerValue);
}
catch (Exception exc) {
final String writer = StringUtil.getThrowableText(exc);
throw new IllegalStateException("myUI=" + myUI + "; cause=" + writer);
}
}
public void setLeadingTabIndex(final int leadingIndex) {
try {
final Field tabScrollerField = BasicTabbedPaneUI.class.getDeclaredField(TAB_SCROLLER_NAME);
tabScrollerField.setAccessible(true);
final Object tabScrollerValue = tabScrollerField.get(myUI);
Method setLeadingIndexMethod = null;
final Method[] methods = tabScrollerValue.getClass().getDeclaredMethods();
for (final Method method : methods) {
if (SET_LEADING_TAB_INDEX_METHOD.equals(method.getName())) {
setLeadingIndexMethod = method;
break;
}
}
if (setLeadingIndexMethod == null) {
throw new IllegalStateException("method setLeadingTabIndex not found");
}
setLeadingIndexMethod.setAccessible(true);
setLeadingIndexMethod.invoke(tabScrollerValue, Integer.valueOf(getTabPlacement()), Integer.valueOf(leadingIndex));
}
catch (Exception exc) {
final String writer = StringUtil.getThrowableText(exc);
throw new IllegalStateException("myUI=" + myUI + "; cause=" + writer);
}
}
}
@Override
public boolean isDisposed() {
return false;
}
}