/*
* Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr.
*
* This file is part of the SeaGlass Pluggable Look and Feel.
*
* 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.
*
* $Id: SeaGlassTabbedPaneUI.java 1595 2011-08-09 20:33:48Z rosstauscher@gmx.de $
*/
package com.seaglasslookandfeel.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.plaf.synth.ColorType;
import javax.swing.plaf.synth.Region;
import javax.swing.plaf.synth.SynthConstants;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthStyle;
import javax.swing.text.View;
import sun.swing.SwingUtilities2;
import com.seaglasslookandfeel.SeaGlassContext;
import com.seaglasslookandfeel.SeaGlassLookAndFeel;
import com.seaglasslookandfeel.SeaGlassRegion;
import com.seaglasslookandfeel.SeaGlassSynthPainterImpl;
import com.seaglasslookandfeel.component.SeaGlassArrowButton;
import com.seaglasslookandfeel.util.ControlOrientation;
import com.seaglasslookandfeel.util.SeaGlassTabCloseListener;
/**
* Sea Glass's TabbedPane UI delegate.
*
* <p>Based on SynthTabbedPaneUI.</p>
*/
public class SeaGlassTabbedPaneUI extends BasicTabbedPaneUI implements SeaglassUI, PropertyChangeListener {
private SeaGlassContext tabAreaContext;
private SeaGlassContext tabContext;
private SeaGlassContext tabCloseContext;
private SeaGlassContext tabContentContext;
private SynthStyle style;
private SynthStyle tabStyle;
private SynthStyle tabCloseStyle;
private SynthStyle tabAreaStyle;
private SynthStyle tabContentStyle;
private Rectangle textRect;
private Rectangle iconRect;
private Rectangle tabAreaRect;
private Rectangle contentRect;
private boolean selectedTabIsPressed = false;
/**
* Actions to be performed when tab close button is pressed and when tab is
* actually closed.
*/
protected SeaGlassTabCloseListener tabCloseListener;
/** The scroll forward button. This may or may not be visible. */
protected SynthScrollableTabButton scrollForwardButton;
/** The scroll backward button. This may or may not be visible. */
protected SynthScrollableTabButton scrollBackwardButton;
/** Margin for the close button. */
protected Insets closeButtonInsets;
/** The size of the close button. */
protected int closeButtonSize;
/** Index of initial displayed tab. */
protected int leadingTabIndex = 0;
/** Index of final displayed tab. */
protected int trailingTabIndex = 0;
/** Side the tabs are on. */
protected int tabPlacement;
/** Horizontal/vertical abstraction. */
protected ControlOrientation orientation;
/**
* Placement of the tab close button: LEFT, RIGHT, or CENTER (the default)
* for none.
*/
protected int tabCloseButtonPlacement;
/** Index of the armed close button, or -1 if none is armed. */
protected int closeButtonArmedIndex;
/**
* Index of the close button the mouse is hovering over, or -1 if none is
* being hovered over.
*/
protected int closeButtonHoverIndex;
/**
* Creates a new SeaGlassTabbedPaneUI object.
*/
SeaGlassTabbedPaneUI() {
textRect = new Rectangle();
iconRect = new Rectangle();
tabAreaRect = new Rectangle();
contentRect = new Rectangle();
}
/**
* Create the UI delegate.
*
* @param c the component (not used).
*
* @return the UI delegate.
*/
public static ComponentUI createUI(JComponent c) {
return new SeaGlassTabbedPaneUI();
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#installUI(javax.swing.JComponent)
*/
@Override
public void installUI(JComponent c) {
super.installUI(c);
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#installComponents()
*/
@Override
protected void installComponents() {
super.installComponents();
scrollBackwardButton = (SynthScrollableTabButton) createScrollButton(WEST);
scrollForwardButton = (SynthScrollableTabButton) createScrollButton(EAST);
tabPane.add(scrollBackwardButton);
tabPane.add(scrollForwardButton);
scrollBackwardButton.setVisible(false);
scrollForwardButton.setVisible(false);
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallUI(javax.swing.JComponent)
*/
@Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallComponents()
*/
@Override
protected void uninstallComponents() {
super.uninstallComponents();
if (scrollBackwardButton != null) {
tabPane.remove(scrollBackwardButton);
scrollBackwardButton = null;
}
if (scrollForwardButton != null) {
tabPane.remove(scrollForwardButton);
scrollForwardButton = null;
}
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#installDefaults()
*/
protected void installDefaults() {
leadingTabIndex = 0;
trailingTabIndex = 0;
updateStyle(tabPane);
}
/**
* Update the Synth styles when something changes.
*
* @param c the component.
*/
private void updateStyle(JTabbedPane c) {
SeaGlassContext context = getContext(c, ENABLED);
SynthStyle oldStyle = style;
style = SeaGlassLookAndFeel.updateStyle(context, this);
tabPlacement = tabPane.getTabPlacement();
orientation = ControlOrientation.getOrientation(tabPlacement == LEFT || tabPlacement == RIGHT ? VERTICAL : HORIZONTAL);
closeButtonArmedIndex = -1;
Object o = c.getClientProperty("JTabbedPane.closeButton");
if (o != null && "left".equals(o)) {
tabCloseButtonPlacement = LEFT;
} else if (o != null && "right".equals(o)) {
tabCloseButtonPlacement = RIGHT;
} else {
tabCloseButtonPlacement = CENTER;
}
closeButtonSize = style.getInt(context, "closeButtonSize", 6);
closeButtonInsets = (Insets) style.get(context, "closeButtonInsets");
if (closeButtonInsets == null) {
closeButtonInsets = new Insets(2, 2, 2, 2);
}
o = c.getClientProperty("JTabbedPane.closeListener");
if (o != null && o instanceof SeaGlassTabCloseListener) {
if (tabCloseListener == null) {
tabCloseListener = (SeaGlassTabCloseListener) o;
}
}
// Add properties other than JComponent colors, Borders and
// opacity settings here:
if (style != oldStyle) {
tabRunOverlay = 0;
textIconGap = style.getInt(context, "TabbedPane.textIconGap", 0);
selectedTabPadInsets = (Insets) style.get(context, "TabbedPane.selectedTabPadInsets");
if (selectedTabPadInsets == null) {
selectedTabPadInsets = new Insets(0, 0, 0, 0);
}
if (oldStyle != null) {
uninstallKeyboardActions();
installKeyboardActions();
}
}
context.dispose();
if (tabContext != null) {
tabContext.dispose();
}
tabContext = getContext(c, Region.TABBED_PANE_TAB, ENABLED);
this.tabStyle = SeaGlassLookAndFeel.updateStyle(tabContext, this);
tabInsets = tabStyle.getInsets(tabContext, null);
if (tabCloseContext != null) {
tabCloseContext.dispose();
}
tabCloseContext = getContext(c, SeaGlassRegion.TABBED_PANE_TAB_CLOSE_BUTTON, ENABLED);
this.tabCloseStyle = SeaGlassLookAndFeel.updateStyle(tabCloseContext, this);
if (tabAreaContext != null) {
tabAreaContext.dispose();
}
tabAreaContext = getContext(c, Region.TABBED_PANE_TAB_AREA, ENABLED);
this.tabAreaStyle = SeaGlassLookAndFeel.updateStyle(tabAreaContext, this);
tabAreaInsets = tabAreaStyle.getInsets(tabAreaContext, null);
if (tabContentContext != null) {
tabContentContext.dispose();
}
tabContentContext = getContext(c, Region.TABBED_PANE_CONTENT, ENABLED);
this.tabContentStyle = SeaGlassLookAndFeel.updateStyle(tabContentContext, this);
contentBorderInsets = tabContentStyle.getInsets(tabContentContext, null);
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#installListeners()
*/
protected void installListeners() {
super.installListeners();
tabPane.addMouseMotionListener((MouseAdapter) mouseListener);
tabPane.addPropertyChangeListener(this);
scrollBackwardButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
scrollBackward();
}
});
scrollForwardButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
scrollForward();
}
});
}
/**
* Scroll the tab buttons backwards.
*/
protected void scrollBackward() {
int selectedIndex = tabPane.getSelectedIndex();
if (--selectedIndex < 0) {
tabPane.setSelectedIndex(tabPane.getTabCount() == 0 ? -1 : 0);
} else {
tabPane.setSelectedIndex(selectedIndex);
}
tabPane.repaint();
}
/**
* Scroll the tab buttons forwards.
*/
protected void scrollForward() {
int selectedIndex = tabPane.getSelectedIndex();
if (++selectedIndex >= tabPane.getTabCount()) {
tabPane.setSelectedIndex(tabPane.getTabCount() - 1);
} else {
tabPane.setSelectedIndex(selectedIndex);
}
tabPane.repaint();
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallListeners()
*/
protected void uninstallListeners() {
if (mouseListener != null) {
tabPane.removeMouseMotionListener((MouseAdapter) mouseListener);
}
tabPane.removePropertyChangeListener(this);
super.uninstallListeners();
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallDefaults()
*/
protected void uninstallDefaults() {
SeaGlassContext context = getContext(tabPane, ENABLED);
style.uninstallDefaults(context);
context.dispose();
style = null;
tabStyle.uninstallDefaults(tabContext);
tabContext.dispose();
tabContext = null;
tabStyle = null;
tabAreaStyle.uninstallDefaults(tabAreaContext);
tabAreaContext.dispose();
tabAreaContext = null;
tabAreaStyle = null;
tabContentStyle.uninstallDefaults(tabContentContext);
tabContentContext.dispose();
tabContentContext = null;
tabContentStyle = null;
}
/**
* @see SeaglassUI#getContext(javax.swing.JComponent)
*/
public SeaGlassContext getContext(JComponent c) {
return getContext(c, getComponentState(c));
}
/**
* Create a SynthContext for the component and state. Use the default
* region.
*
* @param c the component.
* @param state the state.
*
* @return the newly created SynthContext.
*/
public SeaGlassContext getContext(JComponent c, int state) {
return SeaGlassContext.getContext(SeaGlassContext.class, c, SeaGlassLookAndFeel.getRegion(c), style, state);
}
/**
* Create a SynthContext for the component and subregion. Use the current
* state.
*
* @param c the component.
* @param subregion the subregion.
*
* @return the newly created SynthContext.
*/
public SeaGlassContext getContext(JComponent c, Region subregion) {
return getContext(c, subregion, getComponentState(c));
}
/**
* Create a SynthContext for the component, subregion, and state.
*
* @param c the component.
* @param subregion the subregion.
* @param state the state.
*
* @return the newly created SynthContext.
*/
private SeaGlassContext getContext(JComponent c, Region subregion, int state) {
SynthStyle style = null;
Class klass = SeaGlassContext.class;
if (subregion == Region.TABBED_PANE_TAB) {
style = tabStyle;
} else if (subregion == Region.TABBED_PANE_TAB_AREA) {
style = tabAreaStyle;
} else if (subregion == Region.TABBED_PANE_CONTENT) {
style = tabContentStyle;
} else if (subregion == SeaGlassRegion.TABBED_PANE_TAB_CLOSE_BUTTON) {
style = tabCloseStyle;
}
return SeaGlassContext.getContext(klass, c, subregion, style, state);
}
/**
* Get the current state for the component.
*
* @param c the component.
*
* @return the component's state.
*/
private int getComponentState(JComponent c) {
return SeaGlassLookAndFeel.getComponentState(c);
}
/**
* Get the state for the specified tab's close button.
*
* @param c the tabbed pane.
* @param tabIndex the index of the tab.
* @param tabIsMousedOver TODO
*
* @return the close button state.
*/
public int getCloseButtonState(JComponent c, int tabIndex, boolean tabIsMousedOver) {
if (!c.isEnabled()) {
return DISABLED;
} else if (tabIndex == closeButtonArmedIndex) {
return PRESSED;
} else if (tabIndex == closeButtonHoverIndex) {
return FOCUSED;
} else if (tabIsMousedOver) {
return MOUSE_OVER;
}
return ENABLED;
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#createScrollButton(int)
*/
protected JButton createScrollButton(int direction) {
SynthScrollableTabButton b = new SynthScrollableTabButton(direction);
b.setName("TabbedPaneTabArea.button" + direction);
return b;
}
/**
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent e) {
if (SeaGlassLookAndFeel.shouldUpdateStyle(e)) {
updateStyle(tabPane);
} else if (e.getPropertyName() == "tabPlacement") {
updateStyle(tabPane);
}
}
/**
* Create the mouse listener.
*
* <p>Overridden to keep track of whether the selected tab is also
* pressed.</p>
*
* @return the mouse listener.
*
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#createMouseListener()
*/
@Override
protected MouseListener createMouseListener() {
return new SeaGlassTabbedPaneMouseHandler(super.createMouseListener());
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#createLayoutManager()
*/
@Override
protected LayoutManager createLayoutManager() {
return new SeaGlassTabbedPaneLayout();
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabLabelShiftX(int, int,
* boolean)
*/
@Override
protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
return 0;
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabLabelShiftY(int, int,
* boolean)
*/
@Override
protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
return 0;
}
/**
* @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics, javax.swing.JComponent)
*/
public void update(Graphics g, JComponent c) {
SeaGlassContext context = getContext(c);
SeaGlassLookAndFeel.update(context, g);
context.getPainter().paintTabbedPaneBackground(context, g, tabAreaRect.x, tabAreaRect.y, tabAreaRect.width, tabAreaRect.height);
paint(context, g);
context.dispose();
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getBaseline(int)
*/
protected int getBaseline(int tab) {
if (tabPane.getTabComponentAt(tab) != null || getTextViewForTab(tab) != null) {
return super.getBaseline(tab);
}
String title = tabPane.getTitleAt(tab);
Font font = tabContext.getStyle().getFont(tabContext);
FontMetrics metrics = getFontMetrics(font);
Icon icon = getIconForTab(tab);
textRect.setBounds(0, 0, 0, 0);
iconRect.setBounds(0, 0, 0, 0);
calcRect.setBounds(0, 0, Short.MAX_VALUE, maxTabHeight);
tabContext.getStyle().getGraphicsUtils(tabContext).layoutText(tabContext, metrics, title, icon, SwingUtilities.CENTER,
SwingUtilities.CENTER, SwingUtilities.LEADING,
SwingUtilities.TRAILING, calcRect, iconRect, textRect, textIconGap);
return textRect.y + metrics.getAscent() + getBaselineOffset();
}
/**
* @see SeaglassUI#paintBorder(javax.swing.plaf.synth.SynthContext,
* java.awt.Graphics, int, int, int, int)
*/
public void paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h) {
((SeaGlassContext) context).getPainter().paintTabbedPaneBorder(context, g, x, y, w, h);
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#paint(java.awt.Graphics, javax.swing.JComponent)
*/
public void paint(Graphics g, JComponent c) {
SeaGlassContext context = getContext(c);
paint(context, g);
context.dispose();
}
/**
* Paint the tabbed pane.
*
* @param context the SynthContext describing the control.
* @param g the Graphics context to paint with.
*/
protected void paint(SeaGlassContext context, Graphics g) {
int selectedIndex = tabPane.getSelectedIndex();
ensureCurrentLayout();
// Paint content border.
paintContentBorder(tabContentContext, g, tabPlacement, selectedIndex);
paintTabArea(g, tabPlacement, selectedIndex);
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabArea(java.awt.Graphics,
* int, int)
*/
protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
paintTabArea(tabAreaContext, g, tabPlacement, selectedIndex, tabAreaRect);
}
/**
* Paint the tab area, including the tabs.
*
* @param ss the SynthContext.
* @param g the Graphics context.
* @param tabPlacement the side the tabs are on.
* @param selectedIndex the current selected tab index.
* @param tabAreaBounds the bounds of the tab area.
*/
protected void paintTabArea(SeaGlassContext ss, Graphics g, int tabPlacement, int selectedIndex, Rectangle tabAreaBounds) {
Rectangle clipRect = g.getClipBounds();
ss.setComponentState(SynthConstants.ENABLED);
// Paint the tab area.
SeaGlassLookAndFeel.updateSubregion(ss, g, tabAreaBounds);
ss.getPainter().paintTabbedPaneTabAreaBackground(ss, g, tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width,
tabAreaBounds.height, tabPlacement);
ss.getPainter().paintTabbedPaneTabAreaBorder(ss, g, tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width, tabAreaBounds.height,
tabPlacement);
iconRect.setBounds(0, 0, 0, 0);
textRect.setBounds(0, 0, 0, 0);
if (runCount == 0) {
return;
}
if (scrollBackwardButton.isVisible()) {
paintScrollButtonBackground(ss, g, scrollBackwardButton);
}
if (scrollForwardButton.isVisible()) {
paintScrollButtonBackground(ss, g, scrollForwardButton);
}
for (int i = leadingTabIndex; i <= trailingTabIndex; i++) {
if (rects[i].intersects(clipRect) && selectedIndex != i) {
paintTab(tabContext, g, rects, i, iconRect, textRect);
}
}
if (selectedIndex >= 0) {
if (rects[selectedIndex].intersects(clipRect)) {
paintTab(tabContext, g, rects, selectedIndex, iconRect, textRect);
}
}
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#setRolloverTab(int)
*/
protected void setRolloverTab(int index) {
int oldRolloverTab = getRolloverTab();
super.setRolloverTab(index);
Rectangle r = null;
if ((oldRolloverTab >= 0) && (oldRolloverTab < tabPane.getTabCount())) {
r = getTabBounds(tabPane, oldRolloverTab);
if (r != null) {
tabPane.repaint(r);
}
}
if (index >= 0) {
r = getTabBounds(tabPane, index);
if (r != null) {
tabPane.repaint(r);
}
}
}
/**
* Paint a tab.
*
* @param ss the SynthContext.
* @param g the Graphics context.
* @param rects the array containing the bounds for the tabs.
* @param tabIndex the tab index to paint.
* @param iconRect the bounds in which to paint the tab icon, if any.
* @param textRect the bounds in which to paint the tab text, if any.
*/
protected void paintTab(SeaGlassContext ss, Graphics g, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect) {
Rectangle tabRect = rects[tabIndex];
int selectedIndex = tabPane.getSelectedIndex();
boolean isSelected = selectedIndex == tabIndex;
JComponent b = ss.getComponent();
boolean flipSegments = (orientation == ControlOrientation.HORIZONTAL && !tabPane.getComponentOrientation().isLeftToRight());
String segmentPosition = "only";
if (tabPane.getTabCount() > 1) {
if (tabIndex == 0 && tabIndex == leadingTabIndex) {
segmentPosition = flipSegments ? "last" : "first";
} else if (tabIndex == tabPane.getTabCount() - 1 && tabIndex == trailingTabIndex) {
segmentPosition = flipSegments ? "first" : "last";
} else {
segmentPosition = "middle";
}
}
b.putClientProperty("JTabbedPane.Tab.segmentPosition", segmentPosition);
updateTabContext(tabIndex, isSelected, isSelected && selectedTabIsPressed, getRolloverTab() == tabIndex,
getFocusIndex() == tabIndex);
SeaGlassLookAndFeel.updateSubregion(ss, g, tabRect);
int x = tabRect.x;
int y = tabRect.y;
int height = tabRect.height;
int width = tabRect.width;
tabContext.getPainter().paintTabbedPaneTabBackground(tabContext, g, x, y, width, height, tabIndex, tabPlacement);
tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g, x, y, width, height, tabIndex, tabPlacement);
if (tabCloseButtonPlacement != CENTER) {
tabRect = paintCloseButton(g, tabContext, tabIndex);
}
if (tabPane.getTabComponentAt(tabIndex) == null) {
String title = tabPane.getTitleAt(tabIndex);
Font font = ss.getStyle().getFont(ss);
FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font);
Icon icon = getIconForTab(tabIndex);
layoutLabel(ss, tabPlacement, metrics, tabIndex, title, icon, tabRect, iconRect, textRect, isSelected);
paintText(ss, g, tabPlacement, font, metrics, tabIndex, title, textRect, isSelected);
paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
}
}
/**
* Paint the close button for a tab.
*
* @param g the Graphics context.
* @param tabContext the SynthContext for the tab itself.
* @param tabIndex the tab index to paint.
*
* @return the new tab bounds.
*/
protected Rectangle paintCloseButton(Graphics g, SynthContext tabContext, int tabIndex) {
Rectangle tabRect = new Rectangle(rects[tabIndex]);
Rectangle bounds = getCloseButtonBounds(tabIndex);
int offset = bounds.width + textIconGap;
boolean onLeft = isCloseButtonOnLeft();
if (onLeft) {
tabRect.x += offset;
tabRect.width -= offset;
} else {
tabRect.width -= offset;
}
SeaGlassContext subcontext = getContext(tabPane, SeaGlassRegion.TABBED_PANE_TAB_CLOSE_BUTTON,
getCloseButtonState(tabPane, tabIndex, (tabContext.getComponentState() & MOUSE_OVER) != 0));
SeaGlassLookAndFeel.updateSubregion(subcontext, g, bounds);
SeaGlassSynthPainterImpl painter = (SeaGlassSynthPainterImpl) subcontext.getPainter();
painter.paintSearchButtonForeground(subcontext, g, bounds.x, bounds.y, bounds.width, bounds.height);
subcontext.dispose();
return tabRect;
}
/**
* Paint the background for a tab scroll button.
*
* @param ss the tab subregion SynthContext.
* @param g the Graphics context.
* @param scrollButton the button to paint.
*/
protected void paintScrollButtonBackground(SeaGlassContext ss, Graphics g, JButton scrollButton) {
Rectangle tabRect = scrollButton.getBounds();
int x = tabRect.x;
int y = tabRect.y;
int height = tabRect.height;
int width = tabRect.width;
boolean flipSegments = (orientation == ControlOrientation.HORIZONTAL && !tabPane.getComponentOrientation().isLeftToRight());
SeaGlassLookAndFeel.updateSubregion(ss, g, tabRect);
tabPane.putClientProperty("JTabbedPane.Tab.segmentPosition",
((scrollButton == scrollBackwardButton) ^ flipSegments) ? "first" : "last");
int oldState = tabContext.getComponentState();
ButtonModel model = scrollButton.getModel();
int isPressed = model.isPressed() && model.isArmed() ? PRESSED : 0;
int buttonState = SeaGlassLookAndFeel.getComponentState(scrollButton) | isPressed;
tabContext.setComponentState(buttonState);
tabContext.getPainter().paintTabbedPaneTabBackground(tabContext, g, x, y, width, height, -1, tabPlacement);
tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g, x, y, width, height, -1, tabPlacement);
tabContext.setComponentState(oldState);
}
/**
* Layout label text for a tab.
*
* @param ss the SynthContext.
* @param tabPlacement the side the tabs are on.
* @param metrics the font metrics.
* @param tabIndex the index of the tab to lay out.
* @param title the text for the label, if any.
* @param icon the icon for the label, if any.
* @param tabRect Rectangle to layout text and icon in.
* @param iconRect Rectangle to place icon bounds in
* @param textRect Rectangle to place text in
* @param isSelected is the tab selected?
*/
protected void layoutLabel(SeaGlassContext ss, int tabPlacement, FontMetrics metrics, int tabIndex, String title, Icon icon,
Rectangle tabRect, Rectangle iconRect, Rectangle textRect, boolean isSelected) {
View v = getTextViewForTab(tabIndex);
if (v != null) {
tabPane.putClientProperty("html", v);
}
textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
ss.getStyle().getGraphicsUtils(ss).layoutText(ss, metrics, title, icon, SwingUtilities.CENTER, SwingUtilities.CENTER,
SwingUtilities.LEADING, SwingUtilities.TRAILING, tabRect, iconRect, textRect,
textIconGap);
tabPane.putClientProperty("html", null);
int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
iconRect.x += xNudge;
iconRect.y += yNudge;
textRect.x += xNudge;
textRect.y += yNudge;
}
/**
* Paint the label text for a tab.
*
* @param ss the SynthContext.
* @param g the Graphics context.
* @param tabPlacement the side the tabs are on.
* @param font the font to use.
* @param metrics the font metrics.
* @param tabIndex the index of the tab to lay out.
* @param title the text for the label, if any.
* @param textRect Rectangle to place text in
* @param isSelected is the tab selected?
*/
protected void paintText(SeaGlassContext ss, Graphics g, int tabPlacement, Font font, FontMetrics metrics, int tabIndex, String title,
Rectangle textRect, boolean isSelected) {
g.setFont(font);
View v = getTextViewForTab(tabIndex);
if (v != null) {
// html
v.paint(g, textRect);
} else {
// plain text
int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
FontMetrics fm = SwingUtilities2.getFontMetrics(tabPane, g);
title = SwingUtilities2.clipStringIfNecessary(tabPane, fm, title, textRect.width);
g.setColor(ss.getStyle().getColor(ss, ColorType.TEXT_FOREGROUND));
ss.getStyle().getGraphicsUtils(ss).paintText(ss, g, title, textRect, mnemIndex);
}
}
/**
* Paint the content pane's border.
*
* @param ss the SynthContext.
* @param g the Graphics context.
* @param tabPlacement the side the tabs are on.
* @param selectedIndex the current selected tab index.
*/
protected void paintContentBorder(SeaGlassContext ss, Graphics g, int tabPlacement, int selectedIndex) {
int width = tabPane.getWidth();
int height = tabPane.getHeight();
Insets insets = tabPane.getInsets();
int x = insets.left;
int y = insets.top;
int w = width - insets.right - insets.left;
int h = height - insets.top - insets.bottom;
switch (tabPlacement) {
case LEFT:
x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
w -= (x - insets.left);
break;
case RIGHT:
w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
break;
case BOTTOM:
h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
break;
case TOP:
default:
y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
h -= (y - insets.top);
}
SeaGlassLookAndFeel.updateSubregion(ss, g, new Rectangle(x, y, w, h));
ss.getPainter().paintTabbedPaneContentBackground(ss, g, x, y, w, h);
ss.getPainter().paintTabbedPaneContentBorder(ss, g, x, y, w, h);
}
/**
* Make sure we have laid out the pane with the current layout.
*/
private void ensureCurrentLayout() {
if (!tabPane.isValid()) {
tabPane.validate();
}
/*
* If tabPane doesn't have a peer yet, the validate() call will silently
* fail. We handle that by forcing a layout if tabPane is still invalid.
* See bug 4237677.
*/
if (!tabPane.isValid()) {
TabbedPaneLayout layout = (TabbedPaneLayout) tabPane.getLayout();
layout.calculateLayoutInfo();
}
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateMaxTabHeight(int)
*/
public int calculateMaxTabHeight(int tabPlacement) {
FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont(tabContext));
int tabCount = tabPane.getTabCount();
int result = 0;
int fontHeight = metrics.getHeight();
for (int i = 0; i < tabCount; i++) {
result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
}
return result;
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateTabWidth(int, int,
* java.awt.FontMetrics)
*/
protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
Icon icon = getIconForTab(tabIndex);
Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
int width = tabInsets.left + tabInsets.right + 3;
Component tabComponent = tabPane.getTabComponentAt(tabIndex);
if (tabComponent != null) {
width += tabComponent.getPreferredSize().width;
if (tabIndex < rects.length && tabCloseButtonPlacement != CENTER) {
width += getCloseButtonBounds(tabIndex).width + textIconGap;
}
} else {
if (icon != null) {
width += icon.getIconWidth() + textIconGap;
}
// Hack to prevent array index out of bounds before the size has been set and the rectangles created.
if (tabIndex < rects.length && tabCloseButtonPlacement != CENTER) {
width += getCloseButtonBounds(tabIndex).width + textIconGap;
}
View v = getTextViewForTab(tabIndex);
if (v != null) {
// html
width += (int) v.getPreferredSpan(View.X_AXIS);
} else {
// plain text
String title = tabPane.getTitleAt(tabIndex);
width += tabContext.getStyle().getGraphicsUtils(tabContext).computeStringWidth(tabContext, metrics.getFont(), metrics,
title);
}
}
return width;
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateMaxTabWidth(int)
*/
public int calculateMaxTabWidth(int tabPlacement) {
FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont(tabContext));
int tabCount = tabPane.getTabCount();
int result = 0;
for (int i = 0; i < tabCount; i++) {
result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
}
return result;
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabInsets(int, int)
*/
protected Insets getTabInsets(int tabPlacement, int tabIndex) {
updateTabContext(tabIndex, false, false, false, (getFocusIndex() == tabIndex));
return tabInsets;
}
/**
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#getFontMetrics()
*/
protected FontMetrics getFontMetrics() {
return getFontMetrics(tabContext.getStyle().getFont(tabContext));
}
/**
* Get the font metrics for the font.
*
* @param font the font.
*
* @return the metrics for the font.
*/
protected FontMetrics getFontMetrics(Font font) {
return tabPane.getFontMetrics(font);
}
/**
* Update the SynthContext for the tab area for a specified tab.
*
* @param index the tab to update for.
* @param selected is the tab selected?
* @param isMouseDown is the mouse down?
* @param isMouseOver is the mouse over the tab?
* @param hasFocus do we have focus?
*/
private void updateTabContext(int index, boolean selected, boolean isMouseDown, boolean isMouseOver, boolean hasFocus) {
int state = 0;
if (!tabPane.isEnabled() || !tabPane.isEnabledAt(index)) {
state |= SynthConstants.DISABLED;
if (selected) {
state |= SynthConstants.SELECTED;
}
} else if (selected) {
state |= (SynthConstants.ENABLED | SynthConstants.SELECTED);
if (isMouseOver && UIManager.getBoolean("TabbedPane.isTabRollover")) {
state |= SynthConstants.MOUSE_OVER;
}
} else if (isMouseOver) {
state |= (SynthConstants.ENABLED | SynthConstants.MOUSE_OVER);
} else {
state = SeaGlassLookAndFeel.getComponentState(tabPane);
state &= ~SynthConstants.FOCUSED; // Don't use tabbedpane focus state.
}
if (hasFocus && tabPane.hasFocus()) {
state |= SynthConstants.FOCUSED; // individual tab has focus
}
if (isMouseDown) {
state |= SynthConstants.PRESSED;
}
tabContext.setComponentState(state);
}
/**
* Determine whether the mouse is over a tab close button, and if so, set
* the hover index.
*
* @param x the current mouse x coordinate.
* @param y the current mouse y coordinate.
*
* @return {@code true} if the mouse is over a close button, {@code false}
* otherwise.
*/
protected boolean isOverCloseButton(int x, int y) {
if (tabCloseButtonPlacement != CENTER) {
int tabCount = tabPane.getTabCount();
for (int i = 0; i < tabCount; i++) {
if (getCloseButtonBounds(i).contains(x, y)) {
closeButtonHoverIndex = i;
return true;
}
}
}
closeButtonHoverIndex = -1;
return false;
}
/**
* Get the bounds for a tab close button.
*
* @param tabIndex the tab index.
*
* @return the bounds.
*/
protected Rectangle getCloseButtonBounds(int tabIndex) {
Rectangle bounds = new Rectangle(rects[tabIndex]);
bounds.width = closeButtonSize;
bounds.height = closeButtonSize;
bounds.y += (rects[tabIndex].height - closeButtonSize - closeButtonInsets.top - closeButtonInsets.bottom) / 2
+ closeButtonInsets.top;
boolean onLeft = isCloseButtonOnLeft();
if (onLeft) {
int offset = orientation == ControlOrientation.VERTICAL || tabIndex == 0 ? 6 : 3;
bounds.x += offset;
} else {
int offset = orientation == ControlOrientation.VERTICAL || tabIndex == tabPane.getTabCount() - 1 ? 6 : 4;
bounds.x += rects[tabIndex].width - bounds.width - offset;
}
return bounds;
}
/**
* Return {@code true} if the tab close button should be placed on the left,
* {@code false} otherwise.
*
* @return {@code true} if the tab close button should be placed on the
* left, {@code false} otherwise.
*/
private boolean isCloseButtonOnLeft() {
return (tabCloseButtonPlacement == LEFT) == tabPane.getComponentOrientation().isLeftToRight();
}
/**
* Called when a close tab button is pressed.
*
* @param tabIndex TODO
*/
protected void doClose(int tabIndex) {
if (tabCloseListener == null || tabCloseListener.tabAboutToBeClosed(tabIndex)) {
String title = tabPane.getTitleAt(tabIndex);
Component component = tabPane.getComponentAt(tabIndex);
tabPane.removeTabAt(tabIndex);
if (tabCloseListener != null) {
tabCloseListener.tabClosed(title, component);
}
}
}
/**
* The scrollable tab button.
*/
private class SynthScrollableTabButton extends SeaGlassArrowButton implements UIResource {
private static final long serialVersionUID = -3983149584304630486L;
/**
* Creates a new SynthScrollableTabButton object.
*
* @param direction the arrow direction.
*/
public SynthScrollableTabButton(int direction) {
super(direction);
putClientProperty("__arrow_scale__", 0.6);
}
/**
* @see javax.swing.JComponent#getPreferredSize()
*/
@Override
public Dimension getPreferredSize() {
return new Dimension(20, 20);
}
}
/**
* The layout manager.
*/
protected class SeaGlassTabbedPaneLayout extends TabbedPaneLayout {
/**
* @see com.apple.laf.AquaTabbedPaneCopyFromBasicUI$TabbedPaneLayout#preferredTabAreaHeight(int,
* int)
*/
protected int preferredTabAreaHeight(int tabPlacement, int width) {
return calculateMaxTabHeight(tabPlacement);
}
/**
* @see com.apple.laf.AquaTabbedPaneCopyFromBasicUI$TabbedPaneLayout#preferredTabAreaWidth(int,
* int)
*/
protected int preferredTabAreaWidth(int tabPlacement, int height) {
return calculateMaxTabWidth(tabPlacement);
}
/**
* @see com.apple.laf.AquaTabbedPaneCopyFromBasicUI$TabbedPaneLayout#layoutContainer(java.awt.Container)
*/
public void layoutContainer(Container parent) {
setRolloverTab(-1);
setScrollButtonDirections();
calculateLayoutInfo();
boolean shouldChangeFocus = verifyFocus(tabPane.getSelectedIndex());
if (tabPane.getTabCount() <= 0) {
return;
}
calcContentRect();
for (int i = 0; i < tabPane.getComponentCount(); i++) {
Component child = tabPane.getComponent(i);
// Ignore the scroll buttons. They have already been positioned in
// calculateTabRects, which will have been called by calculateLayoutInfo,
// which is called above.
if (child != scrollBackwardButton && child != scrollForwardButton) {
child.setBounds(contentRect);
}
}
setTabContainerBounds();
layoutTabComponents();
if (shouldChangeFocus && !SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent())) {
tabPane.requestFocus();
}
}
/**
* Check if we have a custom tab component container.
* @return the container used to hold custom tab components.
*/
private Component getTabContainer() {
int tabCount = tabPane.getTabCount();
for (int i = 0; i < tabCount; i++) {
Component tabComponent = tabPane.getTabComponentAt(i);
if (tabComponent != null) {
return tabComponent.getParent();
}
}
return null;
}
/**
* Set the position of the tab container that is used to layout custom tab components.
*/
private void setTabContainerBounds() {
Component tabContainer = getTabContainer();
if (tabContainer != null) {
int tabContainerX = tabPlacement == RIGHT ? contentRect.width : 0;
int tabContainerY = tabPlacement == BOTTOM ? contentRect.height : 0;
int tabContainerWidth = tabPlacement == LEFT || tabPlacement == RIGHT ? tabPane.getWidth() - contentRect.width : contentRect.width;
int tabContainerHeight = tabPlacement == TOP || tabPlacement == BOTTOM ? tabPane.getHeight() - contentRect.height : contentRect.height;
tabContainer.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
}
}
/**
* Set the directions of the arrows in the scroll buttons if necessary.
*/
private void setScrollButtonDirections() {
if (tabPlacement == LEFT || tabPlacement == RIGHT) {
if (scrollForwardButton.getDirection() != SOUTH) {
scrollForwardButton.setDirection(SOUTH);
}
if (scrollBackwardButton.getDirection() != NORTH) {
scrollBackwardButton.setDirection(NORTH);
}
} else if (tabPane.getComponentOrientation().isLeftToRight()) {
if (scrollForwardButton.getDirection() != EAST) {
scrollForwardButton.setDirection(EAST);
}
if (scrollBackwardButton.getDirection() != WEST) {
scrollBackwardButton.setDirection(WEST);
}
} else {
if (scrollForwardButton.getDirection() != WEST) {
scrollForwardButton.setDirection(WEST);
}
if (scrollBackwardButton.getDirection() != EAST) {
scrollBackwardButton.setDirection(EAST);
}
}
}
/**
* Verify that the currently focused element exists. Reset the focus to
* none if it doesn't. Return whether focus needs to be changed.
*
* @param selectedIndex the current selected index.
*
* @return {@code true} if the focus needs to be changed, {@code false}
* otherwise.
*/
private boolean verifyFocus(int selectedIndex) {
Component visibleComponent = getVisibleComponent();
Component selectedComponent = null;
if (selectedIndex < 0) {
if (visibleComponent != null) {
// The last tab was removed, so remove the component.
setVisibleComponent(null);
}
} else {
selectedComponent = tabPane.getComponentAt(selectedIndex);
}
if (tabPane.getTabCount() == 0) {
return false;
}
boolean shouldChangeFocus = false;
// In order to allow programs to use a single component
// as the display for multiple tabs, we will not change
// the visible compnent if the currently selected tab
// has a null component. This is a bit dicey, as we don't
// explicitly state we support this in the spec, but since
// programs are now depending on this, we're making it work.
if (selectedComponent != null) {
if (selectedComponent != visibleComponent && visibleComponent != null) {
if (findFocusOwner(visibleComponent) != null) {
shouldChangeFocus = true;
}
}
setVisibleComponent(selectedComponent);
}
return shouldChangeFocus;
}
/**
* Calculate the bounds Rectangle for the content panes.
*/
private void calcContentRect() {
Insets contentInsets = getContentBorderInsets(tabPlacement);
Rectangle bounds = tabPane.getBounds();
Insets insets = tabPane.getInsets();
int cx;
int cy;
int cw;
int ch;
switch (tabPlacement) {
case LEFT:
cx = tabAreaRect.x + tabAreaRect.width + contentInsets.left;
cy = tabAreaRect.y + contentInsets.top;
cw = bounds.width - insets.left - insets.right - tabAreaRect.width - contentInsets.left - contentInsets.right;
ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
break;
case RIGHT:
cx = insets.left + contentInsets.left;
cy = insets.top + contentInsets.top;
cw = bounds.width - insets.left - insets.right - tabAreaRect.width - contentInsets.left - contentInsets.right;
ch = bounds.height - insets.top - insets.bottom - contentInsets.top - contentInsets.bottom;
break;
case BOTTOM:
cx = insets.left + contentInsets.left;
cy = insets.top + contentInsets.top;
cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
ch = bounds.height - insets.top - insets.bottom - tabAreaRect.height - contentInsets.top - contentInsets.bottom;
break;
case TOP:
default:
cx = tabAreaRect.x + contentInsets.left;
cy = tabAreaRect.y + tabAreaRect.height + contentInsets.top;
cw = bounds.width - insets.left - insets.right - contentInsets.left - contentInsets.right;
ch = bounds.height - insets.top - insets.bottom - tabAreaRect.height - contentInsets.top - contentInsets.bottom;
}
contentRect.setBounds(cx, cy, cw, ch);
}
/**
* Layout the tab components.
*/
private void layoutTabComponents() {
Rectangle rect = new Rectangle();
for (int i = 0; i < tabPane.getTabCount(); i++) {
Component c = tabPane.getTabComponentAt(i);
if (c == null) {
continue;
}
getTabBounds(i, rect);
Dimension preferredSize = c.getPreferredSize();
Insets insets = getTabInsets(tabPlacement, i);
int outerX = (rect.x-c.getParent().getX()) + insets.left;
int outerY = (rect.y-c.getParent().getY()) + insets.top;
int outerWidth = rect.width - insets.left - insets.right;
int outerHeight = rect.height - insets.top - insets.bottom;
// centralize component
// TODO rossi 30.06.2011 this may need adjustment to respect the optional close buttons
int x = outerX + (outerWidth - preferredSize.width) / 2;
int y = outerY + (outerHeight - preferredSize.height) / 2;
boolean isSelected = i == tabPane.getSelectedIndex();
c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSelected),
y + getTabLabelShiftY(tabPlacement, i, isSelected),
preferredSize.width, preferredSize.height);
}
}
/**
* @see com.apple.laf.AquaTabbedPaneCopyFromBasicUI$TabbedPaneLayout#calculateTabRects(int,
* int)
*/
protected void calculateTabRects(int tabPlacement, int tabCount) {
if (orientation == ControlOrientation.HORIZONTAL) {
maxTabHeight = calculateMaxTabHeight(tabPlacement);
} else {
maxTabWidth = calculateMaxTabWidth(tabPlacement);
}
// Calculate the tab area itself.
calcTabAreaRect();
if (tabCount == 0) {
scrollBackwardButton.setVisible(false);
scrollForwardButton.setVisible(false);
runCount = 0;
selectedRun = -1;
return;
}
selectedRun = 0;
runCount = 1;
int selectedIndex = tabPane.getSelectedIndex();
if (leadingTabIndex > selectedIndex) {
leadingTabIndex = selectedIndex;
}
Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
Dimension size = new Dimension(tabAreaRect.width - tabAreaInsets.left - tabAreaInsets.right,
tabAreaRect.height - tabAreaInsets.top - tabAreaInsets.bottom);
int tabAreaLength = orientation.getLength(size);
int buttonLength = orientation.getLength(scrollForwardButton.getPreferredSize());
determineVisibleTabIndices(tabCount, selectedIndex, tabAreaLength, buttonLength);
resetTabPositionsToLeadingTabIndex(tabCount);
int totalLength = orientation.getPosition(rects[trailingTabIndex].x + rects[trailingTabIndex].width,
rects[trailingTabIndex].y + rects[trailingTabIndex].height);
if (leadingTabIndex > 0 || trailingTabIndex < tabCount - 1) {
resizeTabs(tabCount, totalLength, buttonLength, tabAreaLength);
} else {
centerTabs(tabCount, totalLength, tabAreaLength);
}
// Set the positions and visibility of the scroll buttons.
setScrollButtonPositions(scrollBackwardButton, (leadingTabIndex > 0),
orientation.getPosition(rects[leadingTabIndex]) - buttonLength);
setScrollButtonPositions(scrollForwardButton, (trailingTabIndex < tabCount - 1),
orientation.getPosition(rects[trailingTabIndex]) + orientation.getLength(rects[trailingTabIndex]));
// If component orientation right to left and tab placement is on the top or the bottom,
// flip x positions and adjust by widths.
if (!tabPane.getComponentOrientation().isLeftToRight() && orientation == ControlOrientation.HORIZONTAL) {
flipRightToLeft(tabCount, tabPane.getSize());
}
}
/**
* Calculate the rectangle into which the tabs will be drawn. This does
* not include the tab area insets, but does include the tab pane
* insets.
*
* <p>This is used for painting the background as well as for laying out
* the tabs.</p>
*/
private void calcTabAreaRect() {
Insets insets = tabPane.getInsets();
Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
Rectangle bounds = tabPane.getBounds();
if (tabPane.getTabCount() == 0) {
tabAreaRect.setBounds(0, 0, 0, 0);
return;
}
// Calculate bounds within which a tab run must fit.
int position;
int offset;
int length;
int thickness;
if (orientation == ControlOrientation.HORIZONTAL) {
length = bounds.width - insets.left - insets.right;
position = insets.left;
thickness = maxTabHeight + tabAreaInsets.top + tabAreaInsets.bottom;
offset = (tabPlacement == BOTTOM) ? bounds.height - insets.bottom - thickness : insets.top;
} else {
length = bounds.height - insets.top - insets.bottom;
position = insets.top;
thickness = maxTabWidth + tabAreaInsets.left + tabAreaInsets.right;
offset = (tabPlacement == RIGHT) ? bounds.width - insets.right - thickness : insets.left;
}
tabAreaRect.setBounds(orientation.createBounds(position, offset, length, thickness));
}
/**
* Calculate the leading and trailing tab indices that will fit in the
* length, keeping the selected index visible.
*
* @param tabCount the number of tabs.
* @param selectedIndex the current tab.
* @param tabAreaLength the length of the tab area. This takes the tab
* area insets into account.
* @param buttonLength the length of a scroll button. They are both the
* same length.
*/
private void determineVisibleTabIndices(int tabCount, int selectedIndex, int tabAreaLength, int buttonLength) {
int desiredMaximumLength = calcDesiredMaximumLength(tabCount);
int leadingTabOffset = orientation.getPosition(rects[leadingTabIndex]);
int selectedTabEndOffset = orientation.getPosition(rects[selectedIndex].x + rects[selectedIndex].width,
rects[selectedIndex].y + rects[selectedIndex].height);
if (desiredMaximumLength <= tabAreaLength) {
// Fits with no scroll buttons.
leadingTabIndex = 0;
trailingTabIndex = tabCount - 1;
} else if (desiredMaximumLength - leadingTabOffset + buttonLength <= tabAreaLength) {
// Fits from current leading tab index, with scroll backward button. Leave leadingTabIndex alone.
trailingTabIndex = tabCount - 1;
} else if ((leadingTabIndex == 0 && selectedTabEndOffset - leadingTabOffset + buttonLength <= tabAreaLength)
|| (selectedTabEndOffset - leadingTabOffset + 2 * buttonLength <= tabAreaLength)) {
// Selected index fits with current leading tab index and one or two scroll buttons. Leave leadingTabIndex alone.
trailingTabIndex = -1;
for (int i = tabCount - 1; i > selectedIndex; i--) {
int end = orientation.getPosition(rects[i].x + rects[i].width, rects[i].y + rects[i].height);
if (end - leadingTabOffset + 2 * buttonLength <= tabAreaLength) {
trailingTabIndex = i;
break;
}
}
if (trailingTabIndex == -1) {
trailingTabIndex = selectedIndex;
}
} else {
// Selected index does not fit with current leading index and two scroll buttons.
// Make selected index the trailing index and find the leading index that will fit.
trailingTabIndex = selectedIndex;
leadingTabIndex = -1;
for (int i = 0; i < selectedIndex; i++) {
int start = orientation.getPosition(rects[i]);
if (selectedTabEndOffset - start + 2 * buttonLength <= tabAreaLength) {
leadingTabIndex = i;
break;
}
}
if (leadingTabIndex == -1) {
leadingTabIndex = selectedIndex;
}
}
tabRuns[0] = leadingTabIndex;
}
/**
* Run through tabs and lay them all out in a single run, assigning
* maxTabWidth and maxTabHeight. The offset and thickness are set to
* zero in this method. They will be assigned good values later.
*
* @param tabCount the number of tabs.
*
* @return the maximum width, if tabs run horizontall, otherwise the
* maximum height.
*/
private int calcDesiredMaximumLength(int tabCount) {
FontMetrics metrics = getFontMetrics();
int fontHeight = metrics.getHeight();
Insets tabAreaInsets = getTabAreaInsets(fontHeight);
Point corner = new Point(tabAreaRect.x + tabAreaInsets.left,
tabAreaRect.y + tabAreaInsets.top);
int offset = orientation.getOrthogonalOffset(corner);
int thickness = (orientation == ControlOrientation.HORIZONTAL) ? maxTabWidth : maxTabHeight;
int position = 0;
int maxTabLength = 0;
// Run through tabs and lay them out in a single long run.
for (int i = 0; i < tabCount; i++) {
int length = (orientation == ControlOrientation.HORIZONTAL) ? calculateTabWidth(TOP, i, metrics)
: calculateTabHeight(LEFT, i, fontHeight);
rects[i].setBounds(orientation.createBounds(position, offset, length, thickness));
// Update the maximum length and the next tab position.
maxTabLength = Math.max(maxTabLength, length);
position += length;
}
// Update the BasicTabbedPaneUI length variable.
if (orientation == ControlOrientation.HORIZONTAL) {
maxTabWidth = maxTabLength;
} else {
maxTabHeight = maxTabLength;
}
return position;
}
/**
* Reset the positions of the tabs between leadingTabIndex and
* trailingTabIndex, inclusive, such that the leadingTabIndex is at
* position zero.
*
* @param tabCount the number of tabs.
*/
private void resetTabPositionsToLeadingTabIndex(int tabCount) {
// Rebalance the layout such that the leading tab is at position 0.
int leadingTabPosition = orientation.getPosition(rects[leadingTabIndex]);
for (int i = 0; i < tabCount; i++) {
if (i < leadingTabIndex || i > trailingTabIndex) {
rects[i].setBounds(-1, -1, 0, 0);
} else {
orientation.updateBoundsPosition(rects[i], orientation.getPosition(rects[i]) - leadingTabPosition);
}
}
}
/**
* Center the tabs in the tab area.
*
* @param tabCount the number of tabs.
* @param totalLength the total length available of the tabs.
* @param tabAreaLength the total length available.
*/
private void centerTabs(int tabCount, int totalLength, int tabAreaLength) {
Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
Point corner = new Point(tabAreaRect.x + tabAreaInsets.left, tabAreaRect.y + tabAreaInsets.top);
int startPosition = orientation.getPosition(corner);
int offset = orientation.getOrthogonalOffset(corner);
int thickness = (orientation == ControlOrientation.HORIZONTAL) ? maxTabHeight : maxTabWidth;
int delta = -(tabAreaLength - totalLength) / 2 - startPosition;
for (int i = leadingTabIndex; i <= trailingTabIndex; i++) {
int position = orientation.getPosition(rects[i]) - delta;
int length = orientation.getLength(rects[i]);
rects[i].setBounds(orientation.createBounds(position, offset, length, thickness));
}
}
/**
* Fill out the visible tabs and scroll buttons to fit the available
* length.
*
* @param tabCount the number of tabs.
* @param totalLength the total length available of the tabs.
* @param buttonLength the size of a scroll button.
* @param tabAreaLength the total length available.
*/
private void resizeTabs(int tabCount, int totalLength, int buttonLength, int tabAreaLength) {
// Subtract off the button length from the available length.
if (leadingTabIndex > 0) {
tabAreaLength -= buttonLength;
}
if (trailingTabIndex < tabCount - 1) {
tabAreaLength -= buttonLength;
}
Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
Point corner = new Point(tabAreaRect.x + tabAreaInsets.left, tabAreaRect.y + tabAreaInsets.top);
int startPosition = orientation.getPosition(corner);
int offset = orientation.getOrthogonalOffset(corner);
int thickness = (orientation == ControlOrientation.HORIZONTAL) ? maxTabHeight : maxTabWidth;
// Fill the tabs to the available width.
float multiplier = ((float) tabAreaLength / totalLength);
for (int i = leadingTabIndex; i <= trailingTabIndex; i++) {
int position = (i == leadingTabIndex) ? startPosition + (leadingTabIndex > 0 ? buttonLength : 0)
: orientation.getPosition(rects[i - 1]) + orientation.getLength(rects[i - 1]);
int length = (int) (orientation.getLength(rects[i]) * multiplier);
rects[i].setBounds(orientation.createBounds(position, offset, length, thickness));
}
}
/**
* Set the bounds Rectangle for a scroll button.
*
* @param child the scroll button.
* @param visible whether the button is visible or not.
* @param position the position from the start of the tab run.
*/
private void setScrollButtonPositions(Component child, boolean visible, int position) {
if (visible) {
child.setBounds(orientation.createBounds(position,
orientation.getOrthogonalOffset(rects[leadingTabIndex]),
orientation.getLength(child.getPreferredSize()),
orientation.getThickness(rects[leadingTabIndex])));
}
child.setEnabled(tabPane.isEnabled());
child.setVisible(visible);
}
/**
* Flip the buttons right to left.
*
* @param tabCount the number of tabs.
* @param size the rectangle to fit them in.
*/
private void flipRightToLeft(int tabCount, Dimension size) {
int rightMargin = size.width;
for (int i = 0; i < tabCount; i++) {
rects[i].x = rightMargin - rects[i].x - rects[i].width;
}
if (scrollBackwardButton.isVisible()) {
Rectangle b = scrollBackwardButton.getBounds();
scrollBackwardButton.setLocation(rightMargin - b.x - b.width, b.y);
}
if (scrollForwardButton.isVisible()) {
Rectangle b = scrollForwardButton.getBounds();
scrollForwardButton.setLocation(rightMargin - b.x - b.width, b.y);
}
}
/**
* Find the focus owner of the component.
*
* @param visibleComponent the component.
*
* @return the focus owner of the component.
*/
private Component findFocusOwner(Component visibleComponent) {
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
// Verify that focusOwner is a descendant of visibleComponent.
for (Component temp = focusOwner; temp != null; temp = (temp instanceof Window) ? null : temp.getParent()) {
if (temp == visibleComponent) {
return focusOwner;
}
}
return null;
}
}
/**
* Our Mouse Handler. Delegates to the basic UI's mouse handler if we don't
* need to handle an event.
*/
public class SeaGlassTabbedPaneMouseHandler extends MouseAdapter {
/** Current mouse x coordinate. */
protected transient int currentMouseX;
/** Current mouse y coordinate. */
protected transient int currentMouseY;
private MouseListener delegate;
private MouseMotionListener delegate2;
/**
* Creates a new SeaGlassTabbedPaneMouseHandler object.
*
* @param originalMouseListener the original mouse handler.
*/
public SeaGlassTabbedPaneMouseHandler(MouseListener originalMouseListener) {
delegate = originalMouseListener;
delegate2 = (MouseMotionListener) originalMouseListener;
closeButtonHoverIndex = -1;
closeButtonArmedIndex = -1;
}
/**
* @see java.awt.event.MouseAdapter#mouseClicked(java.awt.event.MouseEvent)
*/
public void mouseClicked(MouseEvent e) {
delegate.mouseClicked(e);
}
/**
* @see java.awt.event.MouseAdapter#mouseEntered(java.awt.event.MouseEvent)
*/
public void mouseEntered(MouseEvent e) {
delegate.mouseEntered(e);
}
/**
* @see java.awt.event.MouseAdapter#mouseExited(java.awt.event.MouseEvent)
*/
public void mouseExited(MouseEvent e) {
delegate.mouseExited(e);
}
/**
* @see java.awt.event.MouseAdapter#mouseMoved(java.awt.event.MouseEvent)
*/
public void mouseMoved(MouseEvent e) {
int oldHoverIndex = closeButtonHoverIndex;
// Test for mouse position and set hover index.
currentMouseX = e.getX();
currentMouseY = e.getY();
isOverCloseButton(currentMouseX, currentMouseY);
if (oldHoverIndex != closeButtonHoverIndex) {
tabPane.repaint();
return;
}
delegate2.mouseMoved(e);
}
/**
* @see java.awt.event.MouseAdapter#mouseDragged(java.awt.event.MouseEvent)
*/
public void mouseDragged(MouseEvent e) {
currentMouseX = e.getX();
currentMouseY = e.getY();
if (closeButtonArmedIndex != -1 && !isOverCloseButton(currentMouseX, currentMouseY)) {
// isOverCloseButton resets closeButtonArmedIndex.
tabPane.repaint();
}
}
/**
* @see java.awt.event.MouseAdapter#mousePressed(java.awt.event.MouseEvent)
*/
public void mousePressed(MouseEvent e) {
if (!tabPane.isEnabled()) {
return;
}
if (!SwingUtilities.isLeftMouseButton(e) || !tabPane.isEnabled()) {
return;
}
int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
currentMouseX = e.getX();
currentMouseY = e.getY();
if (isOverCloseButton(currentMouseX, currentMouseY)) {
closeButtonArmedIndex = tabIndex;
tabPane.repaint();
return;
} else if (closeButtonArmedIndex != -1) {
// isOverCloseButton resets closeButtonArmedIndex.
tabPane.repaint();
return;
}
if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
if (tabIndex == tabPane.getSelectedIndex()) {
// Clicking on selected tab
selectedTabIsPressed = true;
// TODO need to just repaint the tab area!
tabPane.repaint();
}
}
// forward the event (this will set the selected index, or none
// at all
delegate.mousePressed(e);
}
/**
* @see java.awt.event.MouseAdapter#mouseReleased(java.awt.event.MouseEvent)
*/
public void mouseReleased(MouseEvent e) {
if (closeButtonArmedIndex != -1) {
if (isOverCloseButton(currentMouseX, currentMouseY)) {
doClose(closeButtonArmedIndex);
}
closeButtonArmedIndex = -1;
tabPane.repaint();
} else if (selectedTabIsPressed) {
selectedTabIsPressed = false;
// TODO need to just repaint the tab area!
tabPane.repaint();
}
// forward the event
delegate.mouseReleased(e);
// hack: The super method *should* be setting the mouse-over
// property correctly here, but it doesn't. That is, when the
// mouse is released, whatever tab is below the released mouse
// should be in rollover state. But, if you select a tab and
// don't move the mouse, this doesn't happen. Hence, forwarding
// the event.
delegate2.mouseMoved(e);
}
}
}