Java : Eclipse-style tabbedpane

This page last changed on Feb 16, 2006 by Kees de Kooter

I wanted my application to look like eclipse. JGoodies delivers a good part of the looks. Except for the nice tabs. So I decided to start building it myself. Aided by the following very useful example: http://blog.elevenworks.com/?p=4

Note that this is a work in progress!!

First I subclassed JTabbedPane:

package nl.boplicity.swing;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTabbedPane;

import nl.boplicity.bt.orderbuilder.workbench.ui.Editor;
import nl.boplicity.swing.plaf.basic.EclipseTabbedPaneUI;


/**
 * Eclipse style tabbedpane with elipse-look tabs and popup menu on tabs.
 * 
 * FIXME: save dirty tabs first
 * 
 * @author kees
 * @date 9-feb-2006
 *
 */
public class EclipseTabbedPane extends JTabbedPane {

    private static final long serialVersionUID = 1176020466013529902L;
    private JPopupMenu popupMenu;
    private Integer selectedTabIndex;

    public EclipseTabbedPane() {
        super();
        setUI(new EclipseTabbedPaneUI());

        createPopupMenu();

        addMouseListener(new MouseAdapter() {

            @Override
            public void mouseReleased(MouseEvent mouseEvent) {
                EclipseTabbedPane.this.mouseReleased(mouseEvent);
            }

        });
    }

    private void mouseReleased(MouseEvent mouseEvent) {

        if (mouseEvent.isPopupTrigger()) {
            selectedTabIndex = indexAtLocation(mouseEvent.getX(), mouseEvent.getY());

            // Only show for top row
            if (getTabPlacement() == JTabbedPane.TOP) {
                popupMenu.show(this, mouseEvent.getX(), mouseEvent.getY());
            }
        }

    }

    private JPopupMenu createPopupMenu() {

        popupMenu = new JPopupMenu();

        popupMenu.add(new CloseAction("Close"));
        popupMenu.add(new CloseOthersAction("Close Others"));
        popupMenu.add(new CloseAllAction("Close All"));

        return popupMenu;
    }

    private class CloseAction extends AbstractAction {

        private static final long serialVersionUID = -2625928077474199856L;

        public CloseAction(String name) {
            super(name);
        }

        public void actionPerformed(ActionEvent actionEvent) {
            closeTab(selectedTabIndex);
        }
    }

    private class CloseOthersAction extends AbstractAction {

        private static final long serialVersionUID = -2625928077474199856L;

        public CloseOthersAction(String name) {
            super(name);
        }

        public void actionPerformed(ActionEvent actionEvent) {

            // First remove higher indexes 
            int tabCount = getTabCount();

            if (selectedTabIndex < tabCount - 1) {
                for (int i = selectedTabIndex + 1; i < tabCount; i++) {
                    closeTab(selectedTabIndex + 1);
                }
            }

            if (selectedTabIndex > 0) {
                for (int i = 0; i < selectedTabIndex; i++) {
                    closeTab(0);
                }
            }
        }
    }

    private class CloseAllAction extends AbstractAction {

        private static final long serialVersionUID = -2625928077474199856L;

        public CloseAllAction(String name) {
            super(name);
        }

        public void actionPerformed(ActionEvent actionEvent) {

            int tabCount = getTabCount();

            for (int i = 0; i < tabCount; i++) {
                closeTab(0);
            }
        }
    }
}

The icing on the cake is created by a custom look and feel:

package nl.boplicity.swing.plaf.basic;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.Rectangle;

import javax.swing.Icon;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicTabbedPaneUI;

/**
 * Implements the Eclipse-style tabs.
 * 
 * <ul>
 * <li>Border turns blue it the pane has focus, white if not.</li>
 * <li>Selected tab is blue.</li>
 * <li>A close button is visible on the right if the tab is selected, is shows up in white when 
 * the mouse hovers over an unslected tab. It turns red if the mouse hovers over
 * the selected tab's close button.</li>
 * <li>The tab displays an icon representing the content of the panel on the left 
 * (Default tabbed pane functionality).</li>
 * </ul>
 * 
 * Credits:<br/>
 * Jon Lipsky @see http://blog.elevenworks.com/?p=4<br/>
 * 
 * TODO: create close buttons
 * TODO: shrink size of tabs if the bar is full
 * TODO: add eclipse style dropdown if tab bar is full instead of left-right buttons
 *
 * FIXME: right and bottom edges are not painted
 * FIXME: for now the tabbed pane relies on the pane it is contained in to 
 * have a border.
 * 
 * @author kees
 * @date 17-jan-2006
 *
 */
public class EclipseTabbedPaneUI extends BasicTabbedPaneUI {

    private final Color SELECTED_TAB_COLOR = new Color(10, 36, 106);

    /**
     * FIXME: selected border has rounded top corners
     * 
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabBorder(java.awt.Graphics, int, int, int, int, int, int, boolean)
     */
    @Override
    protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, 
            int x, int y, int w, int h, boolean isSelected) {

        g.setColor(Color.GRAY);

        if (tabPlacement == BOTTOM) {
            g.drawLine(x, y + h, x + w, y + h);
        }

        // right
        g.drawLine(x + w - 1, y, x + w - 1, y + h);

        if (tabPlacement == TOP) {
            // And a white line to the left and top
            g.setColor(Color.WHITE);
            g.drawLine(x, y, x, y + h);

            g.drawLine(x, y, x + w - 2, y);
        }

        if (tabPlacement == BOTTOM && isSelected) {
            g.setColor(Color.WHITE);

            // Top
            g.drawLine(x + 1, y + 1, x + 1, y + h);
            // Right
            g.drawLine(x + w - 2, y, x + w - 2, y + h);
            // Left
            g.drawLine(x + 1, y + 1, x + w - 2, y + 1);
            // Bottom
            g.drawLine(x + 1, y + h - 1, x + w - 2, y + h - 1);
        }
    }

    /**
     * Give selected tab blue color with a gradient!!.
     * 
     * FIXME: with Plastic L&F the unselected background is too dark
     * 
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabBackground(java.awt.Graphics, int, int, int, int, int, int, boolean)
     */
    @Override
    protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, 
            int x, int y, int w, int h, boolean isSelected) {

        Color color = UIManager.getColor("control");

        if (isSelected) {
            if (tabPlacement == TOP) {
                Graphics2D g2 = (Graphics2D)g;
                Paint storedPaint = g2.getPaint();
                g2.setPaint(new GradientPaint(x, y, SELECTED_TAB_COLOR, x + w, y + h, color));
                g2.fillRect(x, y, w, h);
                g2.setPaint(storedPaint);
            }
        } else {
            g.setColor(color);
            g.fillRect(x, y, w - 1, h);
        }
    }

    /**
     * Do not paint a focus indicator.
     * 
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#paintFocusIndicator(java.awt.Graphics, int, java.awt.Rectangle[], int, java.awt.Rectangle, java.awt.Rectangle, boolean)
     */
    @Override
    protected void paintFocusIndicator(Graphics arg0, int arg1, Rectangle[] arg2, int arg3, Rectangle arg4, Rectangle arg5, boolean arg6) {
        // Leave it
    }

    /**
     * We do not want the tab to "lift up" when it is selected.
     * 
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installDefaults()
     */
    @Override
    protected void installDefaults() {
        super.installDefaults();

        tabAreaInsets = new Insets(0, 0, 0, 0);
        selectedTabPadInsets = new Insets(0, 0, 0, 0);
        contentBorderInsets = new Insets(1, 0, 0, 0);
    }

    /**
     * Nor do we want the label to move.
     */
    @Override
    protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {

        return 0;
    }

    /**
     * Increase the tab height a bit
     */
    @Override
    protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {

        return fontHeight + 10;
    }

    @Override
    protected void layoutLabel(int arg0, FontMetrics arg1, int arg2, String arg3, Icon arg4, Rectangle arg5, Rectangle arg6, Rectangle arg7, boolean arg8) {

        super.layoutLabel(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
    }

    /**
     * Selected labels have a white color.
     * 
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#paintText(java.awt.Graphics, int, java.awt.Font, java.awt.FontMetrics, int, java.lang.String, java.awt.Rectangle, boolean)
     */
    @Override
    protected void paintText(Graphics g, int tabPlacement, Font font, 
            FontMetrics metrics, int tabIndex, String title, Rectangle textRect, 
            boolean isSelected) {

        if (isSelected && tabPlacement == TOP) {
            g.setColor(Color.WHITE);
        } else {
            g.setColor(Color.BLACK);
        }

        // HACK: Force painting of Tahoma - Plastic L&F renders a big Arial
        Font tabFont = new Font("Tahoma", Font.PLAIN, 11);

        g.setFont(tabFont);
        g.drawString(title, textRect.x, textRect.y + metrics.getAscent());
    }        

    @Override
    protected void paintContentBorderTopEdge(Graphics g, int tabPlacement, 
            int selectedIndex, int x, int y, int w, int h) {

        if (selectedIndex != -1 && tabPlacement == TOP) {
            g.setColor(Color.GRAY);
            g.drawLine(x, y, x + w, y);
        }
    }

    @Override
    protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement, 
            int selectedIndex, int x, int y, int w, int h) {

        g.setColor(Color.GRAY);        
        g.drawLine(x, y + h, x + w, y + h);
    }

    @Override
    protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement, 
            int selectedIndex, int x, int y, int w, int h) {
        // do nothingx, y, x, y + h);
    }

    @Override
    protected void paintContentBorderRightEdge(Graphics g, int tabPlacement, 
            int selectedIndex, int x, int y, int w, int h) {
        // do nothing
    }

}