com.jgoodies.looks.plastic.PlasticTabbedPaneUI Maven / Gradle / Ivy
Show all versions of jgoodies-looks Show documentation
/*
* Copyright (c) 2001-2012 JGoodies Karsten Lentzsch. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of JGoodies Karsten Lentzsch nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jgoodies.looks.plastic;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.plaf.metal.MetalTabbedPaneUI;
import javax.swing.text.View;
import com.jgoodies.looks.Options;
/**
* The JGoodies Plastic Look&Feel implementation of
* {@code TabbedPaneUI}. It differs from its superclass
* {@code MetalTabbedPaneUI} in that it paints new tab shapes,
* provides two options, and supports ClearLook.
*
* You can enable or disable icons in tabs globally via
* com.jgoodies.looks.Options.setTabIconsEnabled(boolean).
*
* To disable the content border set
*
* JTabbedPane tabbedPane = new JTabbedPane();
* tabbedPane.putClientProperty(Option.NO_CONTENT_BORDER_KEY, Boolean.TRUE);
*
* To paint embedded tabs use
*
* JTabbedPane tabbedPane = new JTabbedPane();
* tabbedPane.putClientProperty(Option.EMBEDDED_TABS_KEY, Boolean.TRUE);
*
*
* There's a special mode that helps you detect content borders in
* heavily wrapped component hierarchies - such as the NetBeans IDE.
* In this marked mode the content border is painted as a Magenta line.
* You can enable this mode by setting the System property
* markContentBorders to true; in a command line:
*
* java -DmarkContentBorders=true
*
*
* Thanks to Andrej Golovnin for his feedback and suggestions.
*
* @author Karsten Lentzsch
* @version $Revision: 1.15 $
*
* @see Options
*/
public final class PlasticTabbedPaneUI extends MetalTabbedPaneUI {
// State ******************************************************************
/**
* Describes if tabs are painted with or without icons.
*/
private static boolean isTabIconsEnabled = Options.isTabIconsEnabled();
/**
* Describes if we paint no content border or not; is false by default.
* You can disable the content border by setting the client property
* Options.NO_CONTENT_BORDER_KEY to Boolean.TRUE;
*/
private Boolean noContentBorder;
/**
* Describes if we paint tabs in an embedded style that is with
* less decoration; this is false by default.
* You can enable the embedded tabs style by setting the client property
* Options.EMBEDDED_TABS_KEY to Boolean.TRUE.
*/
private Boolean embeddedTabs;
/**
* Holds the renderer that is used to render the tabs.
*/
private AbstractRenderer renderer;
/** For use when tabLayoutPolicy == SCROLL_TAB_LAYOUT. */
private ScrollableTabSupport tabScroller;
/**
* Creates the {@code PlasticTabbedPaneUI}.
*
* @see javax.swing.plaf.ComponentUI#createUI(JComponent)
*/
public static ComponentUI createUI(JComponent tabPane) {
return new PlasticTabbedPaneUI();
}
/**
* Installs the UI.
*
* @see javax.swing.plaf.ComponentUI#installUI(JComponent)
*/
@Override
public void installUI(JComponent c) {
super.installUI(c);
embeddedTabs = (Boolean) c.getClientProperty(Options.EMBEDDED_TABS_KEY);
noContentBorder = (Boolean) c.getClientProperty(Options.NO_CONTENT_BORDER_KEY);
renderer = createRenderer(tabPane);
}
/**
* Uninstalls the UI.
* @see javax.swing.plaf.ComponentUI#uninstallUI(JComponent)
*/
@Override
public void uninstallUI(JComponent c) {
renderer = null;
super.uninstallUI(c);
}
/**
* Creates and installs any required subcomponents for the JTabbedPane.
* Invoked by installUI.
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#installComponents()
*/
@Override
protected void installComponents() {
if (scrollableTabLayoutEnabled()) {
if (tabScroller == null) {
tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
tabPane.add(tabScroller.viewport);
}
}
}
/**
* Removes any installed subcomponents from the JTabbedPane.
* Invoked by uninstallUI.
* @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallComponents()
*/
@Override
protected void uninstallComponents() {
if (scrollableTabLayoutEnabled()) {
tabPane.remove(tabScroller.viewport);
tabPane.remove(tabScroller.scrollForwardButton);
tabPane.remove(tabScroller.scrollBackwardButton);
tabScroller = null;
}
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
// if the layout policy is the SCROLL_TAB_LAYOUT, then replace
// the forward and backward actions, installed in the action map
// in the supper class, by our own.
if (scrollableTabLayoutEnabled()) {
Action forwardAction = new ScrollTabsForwardAction();
Action backwardAction = new ScrollTabsBackwardAction();
ActionMap am = SwingUtilities.getUIActionMap(tabPane);
am.put("scrollTabsForwardAction", forwardAction);
am.put("scrollTabsBackwardAction", backwardAction);
tabScroller.scrollForwardButton.setAction(forwardAction);
tabScroller.scrollBackwardButton.setAction(backwardAction);
}
}
/**
* Checks and answers if content border will be painted.
* This is controlled by the component's client property
* Options.NO_CONTENT_BORDER or Options.EMBEDDED.
*/
private boolean hasNoContentBorder() {
return Boolean.TRUE.equals(noContentBorder);
}
/**
* Checks and answers if tabs are painted with minimal decoration.
*/
private boolean hasEmbeddedTabs() {
return Boolean.TRUE.equals(embeddedTabs);
}
/**
* Creates the renderer used to lay out and paint the tabs.
* @param tabbedPane the UIs component
* @return AbstractRenderer the renderer that will be used to paint
*/
private AbstractRenderer createRenderer(JTabbedPane tabbedPane) {
return hasEmbeddedTabs()
? AbstractRenderer.createEmbeddedRenderer(tabbedPane)
: AbstractRenderer.createRenderer(tabPane);
}
/**
* Creates and answer a handler that listens to property changes.
* Unlike the superclass BasicTabbedPane, the PlasticTabbedPaneUI
* uses an extended Handler.
*/
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return new MyPropertyChangeHandler();
}
@Override
protected ChangeListener createChangeListener() {
return new TabSelectionHandler();
}
/*
* Private helper method for the next three methods.
*/
private void doLayout() {
tabPane.revalidate();
tabPane.repaint();
}
/**
* Updates the renderer and layout. This message is sent by
* my PropertyChangeHandler whenever the tab placement changes.
*/
private void tabPlacementChanged() {
renderer = createRenderer(tabPane);
if (scrollableTabLayoutEnabled()) {
tabScroller.createButtons();
}
doLayout();
}
/**
* Updates the embedded tabs property. This message is sent by
* my PropertyChangeHandler whenever the embedded tabs property changes.
*/
private void embeddedTabsPropertyChanged(Boolean newValue) {
embeddedTabs = newValue;
renderer = createRenderer(tabPane);
doLayout();
}
/**
* Updates the no content border property. This message is sent
* by my PropertyChangeHandler whenever the noContentBorder
* property changes.
*/
private void noContentBorderPropertyChanged(Boolean newValue) {
noContentBorder = newValue;
tabPane.repaint();
}
@Override
public void paint(Graphics g, JComponent c) {
int selectedIndex = tabPane.getSelectedIndex();
int tabPlacement = tabPane.getTabPlacement();
ensureCurrentLayout();
// Paint tab area
// If scrollable tabs are enabled, the tab area will be
// painted by the scrollable tab panel instead.
//
if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
paintTabArea(g, tabPlacement, selectedIndex);
}
// Paint content border
paintContentBorder(g, tabPlacement, selectedIndex);
}
@Override
protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects,
int tabIndex, Rectangle iconRect, Rectangle textRect) {
Rectangle tabRect = rects[tabIndex];
int selectedIndex = tabPane.getSelectedIndex();
boolean isSelected = selectedIndex == tabIndex;
Graphics2D g2 = null;
Polygon cropShape = null;
Shape save = null;
int cropx = 0;
int cropy = 0;
if (scrollableTabLayoutEnabled()) {
if (g instanceof Graphics2D) {
g2 = (Graphics2D) g;
// Render visual for cropped tab edge...
Rectangle viewRect = tabScroller.viewport.getViewRect();
int cropline;
switch (tabPlacement) {
case LEFT:
case RIGHT:
cropline = viewRect.y + viewRect.height;
if (tabRect.y < cropline
&& tabRect.y + tabRect.height > cropline) {
cropShape = createCroppedTabClip(tabPlacement, tabRect,
cropline);
cropx = tabRect.x;
cropy = cropline - 1;
}
break;
case TOP:
case BOTTOM:
default:
cropline = viewRect.x + viewRect.width;
if (tabRect.x < cropline
&& tabRect.x + tabRect.width > cropline) {
cropShape = createCroppedTabClip(tabPlacement, tabRect,
cropline);
cropx = cropline - 1;
cropy = tabRect.y;
}
}
if (cropShape != null) {
save = g.getClip();
g2.clip(cropShape);
}
}
}
paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
tabRect.width, tabRect.height, isSelected);
paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
tabRect.width, tabRect.height, isSelected);
String title = tabPane.getTitleAt(tabIndex);
Font font = tabPane.getFont();
FontMetrics metrics = g.getFontMetrics(font);
Icon icon = getIconForTab(tabIndex);
layoutLabel(tabPlacement, metrics, tabIndex, title, icon, tabRect,
iconRect, textRect, isSelected);
paintText(g, tabPlacement, font, metrics, tabIndex, title, textRect,
isSelected);
paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
paintFocusIndicator(g, tabPlacement, rects, tabIndex, iconRect,
textRect, isSelected);
if (cropShape != null) {
paintCroppedTabEdge(g, tabPlacement, tabIndex, isSelected, cropx,
cropy);
g.setClip(save);
}
}
/*
* This method will create and return a polygon shape for the given tab
* rectangle which has been cropped at the specified cropline with a torn
* edge visual. e.g. A "File" tab which has cropped been cropped just after
* the "i":
* -------------
* | ..... |
* | . |
* | ... . |
* | . . |
* | . . |
* | . . |
* --------------
*
* The x, y arrays below define the pattern used to create a "torn" edge
* segment which is repeated to fill the edge of the tab. For tabs placed on
* TOP and BOTTOM, this righthand torn edge is created by line segments
* which are defined by coordinates obtained by subtracting xCropLen[i] from
* (tab.x + tab.width) and adding yCroplen[i] to (tab.y). For tabs placed on
* LEFT or RIGHT, the bottom torn edge is created by subtracting xCropLen[i]
* from (tab.y + tab.height) and adding yCropLen[i] to (tab.x).
*/
private final int[] xCropLen = { 1, 1, 0, 0, 1, 1, 2, 2 };
private final int[] yCropLen = { 0, 3, 3, 6, 6, 9, 9, 12 };
private static final int CROP_SEGMENT = 12;
private Polygon createCroppedTabClip(int tabPlacement, Rectangle tabRect,
int cropline) {
int rlen = 0;
int start = 0;
int end = 0;
int ostart = 0;
switch (tabPlacement) {
case LEFT:
case RIGHT:
rlen = tabRect.width;
start = tabRect.x;
end = tabRect.x + tabRect.width;
ostart = tabRect.y;
break;
case TOP:
case BOTTOM:
default:
rlen = tabRect.height;
start = tabRect.y;
end = tabRect.y + tabRect.height;
ostart = tabRect.x;
}
int rcnt = rlen / CROP_SEGMENT;
if (rlen % CROP_SEGMENT > 0) {
rcnt++;
}
int npts = 2 + rcnt * 8;
int[] xp = new int[npts];
int[] yp = new int[npts];
int pcnt = 0;
xp[pcnt] = ostart;
yp[pcnt++] = end;
xp[pcnt] = ostart;
yp[pcnt++] = start;
for (int i = 0; i < rcnt; i++) {
for (int j = 0; j < xCropLen.length; j++) {
xp[pcnt] = cropline - xCropLen[j];
yp[pcnt] = start + i * CROP_SEGMENT + yCropLen[j];
if (yp[pcnt] >= end) {
yp[pcnt] = end;
pcnt++;
break;
}
pcnt++;
}
}
if (tabPlacement == SwingConstants.TOP
|| tabPlacement == SwingConstants.BOTTOM) {
return new Polygon(xp, yp, pcnt);
}
//LEFT or RIGHT
return new Polygon(yp, xp, pcnt);
}
/* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
* indicating the tab is cropped in the viewport display
*/
private void paintCroppedTabEdge(Graphics g, int tabPlacement,
int tabIndex, boolean isSelected, int x, int y) {
switch (tabPlacement) {
case LEFT:
case RIGHT:
int xx = x;
g.setColor(shadow);
while (xx <= x + rects[tabIndex].width) {
for (int i = 0; i < xCropLen.length; i += 2) {
g.drawLine(xx + yCropLen[i], y - xCropLen[i], xx
+ yCropLen[i + 1] - 1, y - xCropLen[i + 1]);
}
xx += CROP_SEGMENT;
}
break;
case TOP:
case BOTTOM:
default:
int yy = y;
g.setColor(shadow);
while (yy <= y + rects[tabIndex].height) {
for (int i = 0; i < xCropLen.length; i += 2) {
g.drawLine(x - xCropLen[i], yy + yCropLen[i], x
- xCropLen[i + 1], yy + yCropLen[i + 1] - 1);
}
yy += CROP_SEGMENT;
}
}
}
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();
}
}
/**
* Returns the tab index which intersects the specified point
* in the JTabbedPane's coordinate space.
*/
@Override
public int tabForCoordinate(JTabbedPane pane, int x, int y) {
ensureCurrentLayout();
Point p = new Point(x, y);
if (scrollableTabLayoutEnabled()) {
translatePointToTabPanel(x, y, p);
Rectangle viewRect = tabScroller.viewport.getViewRect();
if (!viewRect.contains(p)) {
return -1;
}
}
int tabCount = tabPane.getTabCount();
for (int i = 0; i < tabCount; i++) {
if (rects[i].contains(p.x, p.y)) {
return i;
}
}
return -1;
}
@Override
protected Rectangle getTabBounds(int tabIndex, Rectangle dest) {
dest.width = rects[tabIndex].width;
dest.height = rects[tabIndex].height;
if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
// Need to translate coordinates based on viewport location &
// view position
Point vpp = tabScroller.viewport.getLocation();
Point viewp = tabScroller.viewport.getViewPosition();
dest.x = rects[tabIndex].x + vpp.x - viewp.x;
dest.y = rects[tabIndex].y + vpp.y - viewp.y;
} else { // WRAP_TAB_LAYOUT
dest.x = rects[tabIndex].x;
dest.y = rects[tabIndex].y;
}
return dest;
}
/**
* Returns the index of the tab closest to the passed in location, note
* that the returned tab may not contain the location x,y.
*/
private int getClosestTab(int x, int y) {
int min = 0;
int tabCount = Math.min(rects.length, tabPane.getTabCount());
int max = tabCount;
int tabPlacement = tabPane.getTabPlacement();
boolean useX = tabPlacement == TOP || tabPlacement == BOTTOM;
int want = useX ? x : y;
while (min != max) {
int current = (max + min) / 2;
int minLoc;
int maxLoc;
if (useX) {
minLoc = rects[current].x;
maxLoc = minLoc + rects[current].width;
} else {
minLoc = rects[current].y;
maxLoc = minLoc + rects[current].height;
}
if (want < minLoc) {
max = current;
if (min == max) {
return Math.max(0, current - 1);
}
} else if (want >= maxLoc) {
min = current;
if (max - min <= 1) {
return Math.max(current + 1, tabCount - 1);
}
} else {
return current;
}
}
return min;
}
/**
* Returns a point which is translated from the specified point in the
* JTabbedPane's coordinate space to the coordinate space of the
* ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY.
*/
private Point translatePointToTabPanel(int srcx, int srcy, Point dest) {
Point vpp = tabScroller.viewport.getLocation();
Point viewp = tabScroller.viewport.getViewPosition();
dest.x = srcx - vpp.x + viewp.x;
dest.y = srcy - vpp.y + viewp.y;
return dest;
}
@Override
protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
int tabCount = tabPane.getTabCount();
Rectangle iconRect = new Rectangle(),
textRect = new Rectangle();
Rectangle clipRect = g.getClipBounds();
// Paint tabRuns of tabs from back to front
for (int i = runCount - 1; i >= 0; i--) {
int start = tabRuns[i];
int next = tabRuns[i == runCount - 1 ? 0 : i + 1];
int end = next != 0 ? next - 1 : tabCount - 1;
for (int j = end; j >= start; j--) {
if (j != selectedIndex && rects[j].intersects(clipRect)) {
paintTab(g, tabPlacement, rects, j, iconRect, textRect);
}
}
}
// Paint selected tab if its in the front run
// since it may overlap other tabs
if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) {
paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
}
}
/*
* Copied here from super(super)class to avoid labels being centered on
* vertical tab runs if they consist of icon and text
*/
@Override
protected void layoutLabel(
int tabPlacement,
FontMetrics metrics,
int tabIndex,
String title,
Icon icon,
Rectangle tabRect,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
//fix of issue #4
View v = getTextViewForTab(tabIndex);
if (v != null) {
tabPane.putClientProperty("html", v);
}
Rectangle calcRectangle = new Rectangle(tabRect);
if (isSelected) {
Insets calcInsets = getSelectedTabPadInsets(tabPlacement);
calcRectangle.x += calcInsets.left;
calcRectangle.y += calcInsets.top;
calcRectangle.width -= calcInsets.left + calcInsets.right;
calcRectangle.height -= calcInsets.bottom + calcInsets.top;
}
int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
if ((tabPlacement == RIGHT || tabPlacement == LEFT) && icon != null && title != null && !title.equals("")) {
SwingUtilities.layoutCompoundLabel(
tabPane,
metrics,
title,
icon,
SwingConstants.CENTER,
SwingConstants.LEFT,
SwingConstants.CENTER,
SwingConstants.TRAILING,
calcRectangle,
iconRect,
textRect,
textIconGap);
xNudge += 4;
} else {
SwingUtilities.layoutCompoundLabel(
tabPane,
metrics,
title,
icon,
SwingConstants.CENTER,
SwingConstants.CENTER,
SwingConstants.CENTER,
SwingConstants.TRAILING,
calcRectangle,
iconRect,
textRect,
textIconGap);
iconRect.y += calcRectangle.height % 2;
}
//fix of issue #4
tabPane.putClientProperty("html", null);
iconRect.x += xNudge;
iconRect.y += yNudge;
textRect.x += xNudge;
textRect.y += yNudge;
}
/**
* Answers the icon for the tab with the specified index.
* In case, we have globally switched of the use tab icons,
* we answer {@code null} if and only if we have a title.
*/
@Override
protected Icon getIconForTab(int tabIndex) {
String title = tabPane.getTitleAt(tabIndex);
boolean hasTitle = title != null && title.length() > 0;
return !isTabIconsEnabled && hasTitle
? null
: super.getIconForTab(tabIndex);
}
/**
* Creates the layout manager used to set the tab's bounds.
*/
@Override
protected LayoutManager createLayoutManager() {
if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
return new TabbedPaneScrollLayout();
}
/* WRAP_TAB_LAYOUT */
return new TabbedPaneLayout();
}
/* In an attempt to preserve backward compatibility for programs
* which have extended BasicTabbedPaneUI to do their own layout, the
* UI uses the installed layoutManager (and not tabLayoutPolicy) to
* determine if scrollTabLayout is enabled.
*/
private boolean scrollableTabLayoutEnabled() {
return tabPane.getLayout() instanceof TabbedPaneScrollLayout;
}
protected boolean isTabInFirstRun(int tabIndex) {
return getRunForTab(tabPane.getTabCount(), tabIndex) == 0;
}
@Override
protected void paintContentBorder(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;
}
// Fill region behind content area
g.setColor(selectColor == null
? tabPane.getBackground()
: selectColor);
g.fillRect(x, y, w, h);
Rectangle selRect;
selRect = selectedIndex < 0 ? null : getTabBounds(selectedIndex, calcRect);
boolean drawBroken = selectedIndex >= 0 && isTabInFirstRun(selectedIndex);
boolean isContentBorderPainted = !hasNoContentBorder();
// It sounds a bit odd to call paintContentBorder with
// a parameter isContentBorderPainted set to false.
// But in this case the part of the border touching the tab
// area will still be painted so best let the renderer decide.
renderer.paintContentBorderTopEdge (g, x, y, w, h, drawBroken, selRect, isContentBorderPainted);
renderer.paintContentBorderLeftEdge (g, x, y, w, h, drawBroken, selRect, isContentBorderPainted);
renderer.paintContentBorderBottomEdge(g, x, y, w, h, drawBroken, selRect, isContentBorderPainted);
renderer.paintContentBorderRightEdge (g, x, y, w, h, drawBroken, selRect, isContentBorderPainted);
}
//
// Here comes a number of methods that are just delegated to the
// appropriate renderer
//
/**
* Returns the insets (i.e. the width) of the content Border.
*/
@Override
protected Insets getContentBorderInsets(int tabPlacement) {
return renderer.getContentBorderInsets(super.getContentBorderInsets(tabPlacement));
}
/**
* Returns the amount by which the Tab Area is inset.
*/
@Override
protected Insets getTabAreaInsets(int tabPlacement) {
return renderer.getTabAreaInsets(super.getTabAreaInsets(tabPlacement));
}
/**
* Returns the amount by which the label should be shifted horizontally.
*/
@Override
protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
return renderer.getTabLabelShiftX(tabIndex, isSelected);
}
/**
* Returns the amount by which the label should be shifted vertically.
*/
@Override
protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
return renderer.getTabLabelShiftY(tabIndex, isSelected);
}
/**
* Returns the amount (in pixels) by which two runs should overlap.
*/
@Override
protected int getTabRunOverlay(int tabPlacement) {
return renderer.getTabRunOverlay(tabRunOverlay);
}
/**
* This boolean controls wheather the given run should be padded to
* use up as much space as the others (with more tabs in them).
*/
@Override
protected boolean shouldPadTabRun(int tabPlacement, int run) {
return renderer.shouldPadTabRun(run, super.shouldPadTabRun(tabPlacement, run));
}
/**
* Returns the amount by which the run number {@code run}
* should be indented. Add six pixels for every run to make
* diagonal lines align.
*/
@Override
protected int getTabRunIndent(int tabPlacement, int run) {
return renderer.getTabRunIndent(run);
}
/**
* Returns the insets for this tab.
*/
@Override
protected Insets getTabInsets(int tabPlacement, int tabIndex) {
return renderer.getTabInsets(tabIndex, tabInsets);
}
/**
* Returns the insets for selected tab.
*/
@Override
protected Insets getSelectedTabPadInsets(int tabPlacement) {
return renderer.getSelectedTabPadInsets();
}
/**
* Draws the rectancle around the Tab label which indicates keyboard focus.
*/
@Override
protected void paintFocusIndicator(
Graphics g,
int tabPlacement,
Rectangle[] rectangles,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
renderer.paintFocusIndicator(g, rectangles, tabIndex, iconRect, textRect, isSelected);
}
/**
* Fills the background of the given tab to make sure overlap of
* tabs is handled correctly.
* Note: that tab backgrounds seem to be painted somewhere else, too.
*/
@Override
protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
renderer.paintTabBackground(g, tabIndex, x, y, w, h, isSelected);
}
/**
* Paints the border for one tab. Gets the bounds of the tab as parameters.
* Note that the result is not clipped so you can paint outside that
* rectangle. Tabs painted later on have a chance to overwrite though.
*/
@Override
protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
renderer.paintTabBorder(g, tabIndex, x, y, w, h, isSelected);
}
/**
* Answers wheather tab runs should be rotated. If true, the layout mechanism
* will move the run containing the selected tab so that it touches
* the content pane.
*/
@Override
protected boolean shouldRotateTabRuns(int tabPlacement) {
return false;
}
private final class TabSelectionHandler implements ChangeListener {
private final Rectangle rect = new Rectangle();
@Override
public void stateChanged(ChangeEvent e) {
JTabbedPane tabPane = (JTabbedPane) e.getSource();
tabPane.revalidate();
tabPane.repaint();
if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
int index = tabPane.getSelectedIndex();
if (index < rects.length && index != -1) {
rect.setBounds(rects[index]);
Point viewPosition = tabScroller.viewport.getViewPosition();
if (rect.x < viewPosition.x) {
rect.x -= renderer.getTabsOverlay();
} else {
rect.x += renderer.getTabsOverlay();
}
tabScroller.tabPanel.scrollRectToVisible(rect);
}
}
}
}
/**
* Catches and handles property change events. In addition to the super
* class behavior we listen to changes of the ancestor, tab placement,
* and JGoodies options for content border, and embedded tabs.
*/
private final class MyPropertyChangeHandler
extends BasicTabbedPaneUI.PropertyChangeHandler {
@Override
public void propertyChange(PropertyChangeEvent e) {
String pName = e.getPropertyName();
if (null == pName) {
return;
}
super.propertyChange(e);
if (pName.equals("tabPlacement")) {
tabPlacementChanged();
return;
}
if (pName.equals(Options.EMBEDDED_TABS_KEY)) {
embeddedTabsPropertyChanged((Boolean) e.getNewValue());
return;
}
if (pName.equals(Options.NO_CONTENT_BORDER_KEY)) {
noContentBorderPropertyChanged((Boolean) e.getNewValue());
return;
}
}
}
/**
* Does all the layout work. The result is stored in the container
* class's instance variables. Mainly the rects[] vector.
*/
private class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout {
@Override
protected void calculateTabRects(int tabPlacement, int tabCount) {
FontMetrics metrics = getFontMetrics();
Dimension size = tabPane.getSize();
Insets insets = tabPane.getInsets();
Insets theTabAreaInsets = getTabAreaInsets(tabPlacement);
int fontHeight = metrics.getHeight();
int selectedIndex = tabPane.getSelectedIndex();
int theTabRunOverlay;
int i, j;
int x, y;
int returnAt;
boolean verticalTabRuns = tabPlacement == LEFT || tabPlacement == RIGHT;
boolean leftToRight = PlasticUtils.isLeftToRight(tabPane);
//
// Calculate bounds within which a tab run must fit
//
switch (tabPlacement) {
case LEFT :
maxTabWidth = calculateMaxTabWidth(tabPlacement);
x = insets.left + theTabAreaInsets.left;
y = insets.top + theTabAreaInsets.top;
returnAt = size.height - (insets.bottom + theTabAreaInsets.bottom);
break;
case RIGHT :
maxTabWidth = calculateMaxTabWidth(tabPlacement);
x = size.width - insets.right - theTabAreaInsets.right - maxTabWidth;
y = insets.top + theTabAreaInsets.top;
returnAt = size.height - (insets.bottom + theTabAreaInsets.bottom);
break;
case BOTTOM :
maxTabHeight = calculateMaxTabHeight(tabPlacement);
x = insets.left + theTabAreaInsets.left;
y = size.height - insets.bottom - theTabAreaInsets.bottom - maxTabHeight;
returnAt = size.width - (insets.right + theTabAreaInsets.right);
break;
case TOP :
default :
maxTabHeight = calculateMaxTabHeight(tabPlacement);
x = insets.left + theTabAreaInsets.left;
y = insets.top + theTabAreaInsets.top;
returnAt = size.width - (insets.right + theTabAreaInsets.right);
break;
}
theTabRunOverlay = getTabRunOverlay(tabPlacement);
runCount = 0;
selectedRun = -1;
//keeps track of where we are in the current run.
//this helps not to rely on fragile positioning
//informaion to find out wheter the active Tab
//is the first in run
int tabInRun = -1;
// make a copy of returnAt for the current run and modify
// that so returnAt may still be used later on
int runReturnAt = returnAt;
if (tabCount == 0) {
return;
}
// Run through tabs and partition them into runs
Rectangle rect;
for (i = 0; i < tabCount; i++) {
rect = rects[i];
tabInRun++;
if (!verticalTabRuns) {
// Tabs on TOP or BOTTOM....
if (i > 0) {
rect.x = rects[i - 1].x + rects[i - 1].width;
} else {
tabRuns[0] = 0;
runCount = 1;
maxTabWidth = 0;
rect.x = x;
// tabInRun = 0;
}
rect.width = calculateTabWidth(tabPlacement, i, metrics);
maxTabWidth = Math.max(maxTabWidth, rect.width);
// Never move a TAB down a run if it is the first in run.
// Even if there isn't enough room, moving it to a fresh
// line won't help.
// if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) {
// Never rely on phisical position information to determine
// logical position (if you can avoid it)
if (tabInRun != 0 && rect.x + rect.width > runReturnAt) {
if (runCount > tabRuns.length - 1) {
expandTabRunsArray();
}
// just created a new run, adjust some counters
tabInRun = 0;
tabRuns[runCount] = i;
runCount++;
rect.x = x;
runReturnAt = runReturnAt - 2 * getTabRunIndent(tabPlacement, runCount);
}
// Initialize y position in case there's just one run
rect.y = y;
rect.height = maxTabHeight /* - 2*/;
} else {
// Tabs on LEFT or RIGHT...
if (i > 0) {
rect.y = rects[i - 1].y + rects[i - 1].height;
} else {
tabRuns[0] = 0;
runCount = 1;
maxTabHeight = 0;
rect.y = y;
// tabInRun = 0;
}
rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
maxTabHeight = Math.max(maxTabHeight, rect.height);
// Never move a TAB over a run if it is the first in run.
// Even if there isn't enough room, moving it to a fresh
// run won't help.
// if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
if (tabInRun != 0 && rect.y + rect.height > runReturnAt) {
if (runCount > tabRuns.length - 1) {
expandTabRunsArray();
}
tabRuns[runCount] = i;
runCount++;
rect.y = y;
tabInRun = 0;
runReturnAt -= 2 * getTabRunIndent(tabPlacement, runCount);
}
// Initialize x position in case there's just one column
rect.x = x;
rect.width = maxTabWidth /* - 2*/;
}
if (i == selectedIndex) {
selectedRun = runCount - 1;
}
}
if (runCount > 1) {
// Re-distribute tabs in case last run has leftover space
//last line flush left is OK
// normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
//don't need to recalculate selectedRun if not changed
// selectedRun = getRunForTab(tabCount, selectedIndex);
// Rotate run array so that selected run is first
if (shouldRotateTabRuns(tabPlacement)) {
rotateTabRuns(tabPlacement, selectedRun);
}
}
// Step through runs from back to front to calculate
// tab y locations and to pad runs appropriately
for (i = runCount - 1; i >= 0; i--) {
int start = tabRuns[i];
int next = tabRuns[i == runCount - 1 ? 0 : i + 1];
int end = next != 0 ? next - 1 : tabCount - 1;
int indent = getTabRunIndent(tabPlacement, i);
if (!verticalTabRuns) {
for (j = start; j <= end; j++) {
rect = rects[j];
rect.y = y;
rect.x += indent;
// try to make tabRunIndent symmetric
// rect.width -= 2* indent + 20;
}
if (shouldPadTabRun(tabPlacement, i)) {
padTabRun(tabPlacement, start, end, returnAt - 2 * indent);
}
if (tabPlacement == BOTTOM) {
y -= maxTabHeight - theTabRunOverlay;
} else {
y += maxTabHeight - theTabRunOverlay;
}
} else {
for (j = start; j <= end; j++) {
rect = rects[j];
rect.x = x;
rect.y += indent;
}
if (shouldPadTabRun(tabPlacement, i)) {
padTabRun(tabPlacement, start, end, returnAt - 2 * indent);
}
if (tabPlacement == RIGHT) {
x -= maxTabWidth - theTabRunOverlay;
} else {
x += maxTabWidth - theTabRunOverlay;
}
}
}
// Pad the selected tab so that it appears raised in front
padSelectedTab(tabPlacement, selectedIndex);
// if right to left and tab placement on the top or
// the bottom, flip x positions and adjust by widths
if (!leftToRight && !verticalTabRuns) {
int rightMargin = size.width - (insets.right + theTabAreaInsets.right);
for (i = 0; i < tabCount; i++) {
rects[i].x = rightMargin - rects[i].x - rects[i].width
+ renderer.getTabsOverlay();
}
}
}
/**
* Overridden to insure the same behavior in JDK 6.0 as in JDK 5.0.
*/
@Override
protected void padSelectedTab(int tabPlacement, int selectedIndex) {
if (selectedIndex >= 0) {
Rectangle selRect = rects[selectedIndex];
Insets padInsets = getSelectedTabPadInsets(tabPlacement);
selRect.x -= padInsets.left;
selRect.width += padInsets.left + padInsets.right;
selRect.y -= padInsets.top;
selRect.height += padInsets.top + padInsets.bottom;
}
}
}
private boolean requestFocusForVisibleComponent() {
Component visibleComponent = getVisibleComponent();
if (visibleComponent.isFocusable()) {
visibleComponent.requestFocus();
return true;
}
if (visibleComponent instanceof JComponent) {
if (((JComponent) visibleComponent).requestDefaultFocus()) {
return true;
}
}
return false;
}
private static class ScrollTabsForwardAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
JTabbedPane pane = null;
Object src = e.getSource();
if (src instanceof JTabbedPane) {
pane = (JTabbedPane) src;
} else if (src instanceof PlasticArrowButton) {
pane = (JTabbedPane) ((PlasticArrowButton) src).getParent();
} else {
return; // shouldn't happen
}
PlasticTabbedPaneUI ui = (PlasticTabbedPaneUI) pane.getUI();
if (ui.scrollableTabLayoutEnabled()) {
ui.tabScroller.scrollForward(pane.getTabPlacement());
}
}
}
private static class ScrollTabsBackwardAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
JTabbedPane pane = null;
Object src = e.getSource();
if (src instanceof JTabbedPane) {
pane = (JTabbedPane) src;
} else if (src instanceof PlasticArrowButton) {
pane = (JTabbedPane) ((PlasticArrowButton) src).getParent();
} else {
return; // shouldn't happen
}
PlasticTabbedPaneUI ui = (PlasticTabbedPaneUI) pane.getUI();
if (ui.scrollableTabLayoutEnabled()) {
ui.tabScroller.scrollBackward(pane.getTabPlacement());
}
}
}
private final class TabbedPaneScrollLayout extends TabbedPaneLayout {
@Override
protected int preferredTabAreaHeight(int tabPlacement, int width) {
return calculateMaxTabHeight(tabPlacement);
}
@Override
protected int preferredTabAreaWidth(int tabPlacement, int height) {
return calculateMaxTabWidth(tabPlacement);
}
@Override
public void layoutContainer(Container parent) {
int tabPlacement = tabPane.getTabPlacement();
int tabCount = tabPane.getTabCount();
Insets insets = tabPane.getInsets();
int selectedIndex = tabPane.getSelectedIndex();
Component visibleComponent = getVisibleComponent();
calculateLayoutInfo();
if (selectedIndex < 0) {
if (visibleComponent != null) {
// The last tab was removed, so remove the component
setVisibleComponent(null);
}
} else {
Component selectedComponent = tabPane.getComponentAt(selectedIndex);
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 (SwingUtilities.findFocusOwner(visibleComponent) != null) {
shouldChangeFocus = true;
}
}
setVisibleComponent(selectedComponent);
}
int tx, ty, tw, th; // tab area bounds
int cx, cy, cw, ch; // content area bounds
Insets contentInsets = getContentBorderInsets(tabPlacement);
Rectangle bounds = tabPane.getBounds();
int numChildren = tabPane.getComponentCount();
if (numChildren > 0) {
switch (tabPlacement) {
case LEFT:
// calculate tab area bounds
tw = calculateTabAreaWidth(tabPlacement, runCount,
maxTabWidth);
th = bounds.height - insets.top - insets.bottom;
tx = insets.left;
ty = insets.top;
// calculate content area bounds
cx = tx + tw + contentInsets.left;
cy = ty + contentInsets.top;
cw = bounds.width - insets.left - insets.right - tw
- contentInsets.left - contentInsets.right;
ch = bounds.height - insets.top - insets.bottom
- contentInsets.top - contentInsets.bottom;
break;
case RIGHT:
// calculate tab area bounds
tw = calculateTabAreaWidth(tabPlacement, runCount,
maxTabWidth);
th = bounds.height - insets.top - insets.bottom;
tx = bounds.width - insets.right - tw;
ty = insets.top;
// calculate content area bounds
cx = insets.left + contentInsets.left;
cy = insets.top + contentInsets.top;
cw = bounds.width - insets.left - insets.right - tw
- contentInsets.left - contentInsets.right;
ch = bounds.height - insets.top - insets.bottom
- contentInsets.top - contentInsets.bottom;
break;
case BOTTOM:
// calculate tab area bounds
tw = bounds.width - insets.left - insets.right;
th = calculateTabAreaHeight(tabPlacement, runCount,
maxTabHeight);
tx = insets.left;
ty = bounds.height - insets.bottom - th;
// calculate content area bounds
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 - th
- contentInsets.top - contentInsets.bottom;
break;
case TOP:
default:
// calculate tab area bounds
tw = bounds.width - insets.left - insets.right;
th = calculateTabAreaHeight(tabPlacement, runCount,
maxTabHeight);
tx = insets.left;
ty = insets.top;
// calculate content area bounds
cx = tx + contentInsets.left;
cy = ty + th + contentInsets.top;
cw = bounds.width - insets.left - insets.right
- contentInsets.left - contentInsets.right;
ch = bounds.height - insets.top - insets.bottom - th
- contentInsets.top - contentInsets.bottom;
}
for (int i = 0; i < numChildren; i++) {
Component child = tabPane.getComponent(i);
if (tabScroller != null && child == tabScroller.viewport) {
JViewport viewport = (JViewport) child;
Rectangle viewRect = viewport.getViewRect();
int vw = tw;
int vh = th;
Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
switch (tabPlacement) {
case LEFT:
case RIGHT:
int totalTabHeight = rects[tabCount - 1].y
+ rects[tabCount - 1].height;
if (totalTabHeight > th) {
// Allow space for scrollbuttons
vh = th > 2 * butSize.height ? th - 2
* butSize.height : 0;
if (totalTabHeight - viewRect.y <= vh) {
// Scrolled to the end, so ensure the
// viewport size is
// such that the scroll offset aligns
// with a tab
vh = totalTabHeight - viewRect.y;
}
}
break;
case BOTTOM:
case TOP:
default:
int totalTabWidth = rects[tabCount - 1].x
+ rects[tabCount - 1].width + renderer.getTabsOverlay();
if (totalTabWidth > tw) {
// Need to allow space for scrollbuttons
vw = tw > 2 * butSize.width ? tw - 2
* butSize.width : 0;
if (totalTabWidth - viewRect.x <= vw) {
// Scrolled to the end, so ensure the
// viewport size is
// such that the scroll offset aligns
// with a tab
vw = totalTabWidth - viewRect.x;
}
}
}
child.setBounds(tx, ty, vw, vh);
} else if (tabScroller != null &&
(child == tabScroller.scrollForwardButton ||
child == tabScroller.scrollBackwardButton)) {
Component scrollbutton = child;
Dimension bsize = scrollbutton.getPreferredSize();
int bx = 0;
int by = 0;
int bw = bsize.width;
int bh = bsize.height;
boolean visible = false;
switch (tabPlacement) {
case LEFT:
case RIGHT:
int totalTabHeight = rects[tabCount - 1].y
+ rects[tabCount - 1].height
+ renderer.getTabsOverlay();
if (totalTabHeight > th) {
visible = true;
bx = tabPlacement == LEFT ? tx + tw
- bsize.width : tx;
by = child == tabScroller.scrollForwardButton ? bounds.height
- insets.bottom - bsize.height
: bounds.height - insets.bottom - 2
* bsize.height;
}
break;
case BOTTOM:
case TOP:
default:
int totalTabWidth = rects[tabCount - 1].x
+ rects[tabCount - 1].width
+ renderer.getTabsOverlay();
if (totalTabWidth > tw) {
visible = true;
bx = child == tabScroller.scrollForwardButton ? bounds.width
- insets.left - bsize.width
: bounds.width - insets.left - 2
* bsize.width;
by = tabPlacement == TOP ? ty + th
- bsize.height : ty;
}
}
child.setVisible(visible);
if (visible) {
child.setBounds(bx, by, bw, bh);
}
} else {
// All content children...
child.setBounds(cx, cy, cw, ch);
}
}
if (shouldChangeFocus) {
if (!requestFocusForVisibleComponent()) {
tabPane.requestFocus();
}
}
}
}
}
@Override
protected void calculateTabRects(int tabPlacement, int tabCount) {
FontMetrics metrics = getFontMetrics();
Dimension size = tabPane.getSize();
Insets insets = tabPane.getInsets();
Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
int fontHeight = metrics.getHeight();
int selectedIndex = tabPane.getSelectedIndex();
boolean verticalTabRuns = tabPlacement == LEFT || tabPlacement == RIGHT;
boolean leftToRight = PlasticUtils.isLeftToRight(tabPane);
int x = tabAreaInsets.left;
int y = tabAreaInsets.top;
int totalWidth = 0;
int totalHeight = 0;
//
// Calculate bounds within which a tab run must fit
//
switch(tabPlacement) {
case LEFT:
case RIGHT:
maxTabWidth = calculateMaxTabWidth(tabPlacement);
break;
case BOTTOM:
case TOP:
default:
maxTabHeight = calculateMaxTabHeight(tabPlacement);
}
runCount = 0;
selectedRun = -1;
if (tabCount == 0) {
return;
}
selectedRun = 0;
runCount = 1;
// Run through tabs and lay them out in a single run
Rectangle rect;
for (int i = 0; i < tabCount; i++) {
rect = rects[i];
if (!verticalTabRuns) {
// Tabs on TOP or BOTTOM....
if (i > 0) {
rect.x = rects[i-1].x + rects[i-1].width;
} else {
tabRuns[0] = 0;
maxTabWidth = 0;
totalHeight += maxTabHeight;
rect.x = x;
}
rect.width = calculateTabWidth(tabPlacement, i, metrics);
totalWidth = rect.x + rect.width + renderer.getTabsOverlay();
maxTabWidth = Math.max(maxTabWidth, rect.width);
rect.y = y;
rect.height = maxTabHeight/* - 2*/;
} else {
// Tabs on LEFT or RIGHT...
if (i > 0) {
rect.y = rects[i-1].y + rects[i-1].height;
} else {
tabRuns[0] = 0;
maxTabHeight = 0;
totalWidth = maxTabWidth;
rect.y = y;
}
rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
totalHeight = rect.y + rect.height;
maxTabHeight = Math.max(maxTabHeight, rect.height);
rect.x = x;
rect.width = maxTabWidth/* - 2*/;
}
}
// Pad the selected tab so that it appears raised in front
padSelectedTab(tabPlacement, selectedIndex);
// if right to left and tab placement on the top or
// the bottom, flip x positions and adjust by widths
if (!leftToRight && !verticalTabRuns) {
int rightMargin = size.width
- (insets.right + tabAreaInsets.right);
for (int i = 0; i < tabCount; i++) {
rects[i].x = rightMargin - rects[i].x - rects[i].width;
}
}
tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
}
}
private final class ScrollableTabSupport implements ActionListener,
ChangeListener {
public ScrollableTabViewport viewport;
public ScrollableTabPanel tabPanel;
public JButton scrollForwardButton;
public JButton scrollBackwardButton;
public int leadingTabIndex;
private final Point tabViewPosition = new Point(0, 0);
ScrollableTabSupport(int tabPlacement) {
viewport = new ScrollableTabViewport();
tabPanel = new ScrollableTabPanel();
viewport.setView(tabPanel);
viewport.addChangeListener(this);
createButtons();
}
/**
* Recreates the scroll buttons and adds them to the TabbedPane.
*/
void createButtons() {
if (scrollForwardButton != null) {
tabPane.remove(scrollForwardButton);
scrollForwardButton.removeActionListener(this);
tabPane.remove(scrollBackwardButton);
scrollBackwardButton.removeActionListener(this);
}
int tabPlacement = tabPane.getTabPlacement();
int width = UIManager.getInt("ScrollBar.width");
if (tabPlacement == TOP || tabPlacement == BOTTOM) {
scrollForwardButton = new ArrowButton(EAST, width);
scrollBackwardButton = new ArrowButton(WEST, width);
} else { // tabPlacement = LEFT || RIGHT
scrollForwardButton = new ArrowButton(SOUTH, width);
scrollBackwardButton = new ArrowButton(NORTH, width);
}
scrollForwardButton.addActionListener(this);
scrollBackwardButton.addActionListener(this);
tabPane.add(scrollForwardButton);
tabPane.add(scrollBackwardButton);
}
public void scrollForward(int tabPlacement) {
Dimension viewSize = viewport.getViewSize();
Rectangle viewRect = viewport.getViewRect();
if (tabPlacement == TOP || tabPlacement == BOTTOM) {
if (viewRect.width >= viewSize.width - viewRect.x) {
return; // no room left to scroll
}
} else { // tabPlacement == LEFT || tabPlacement == RIGHT
if (viewRect.height >= viewSize.height - viewRect.y) {
return;
}
}
setLeadingTabIndex(tabPlacement, leadingTabIndex + 1);
}
public void scrollBackward(int tabPlacement) {
if (leadingTabIndex == 0) {
return; // no room left to scroll
}
setLeadingTabIndex(tabPlacement, leadingTabIndex - 1);
}
public void setLeadingTabIndex(int tabPlacement, int index) {
leadingTabIndex = index;
Dimension viewSize = viewport.getViewSize();
Rectangle viewRect = viewport.getViewRect();
switch (tabPlacement) {
case TOP:
case BOTTOM:
tabViewPosition.x = leadingTabIndex == 0 ? 0
: rects[leadingTabIndex].x - renderer.getTabsOverlay();
if (viewSize.width - tabViewPosition.x < viewRect.width) {
// We've scrolled to the end, so adjust the viewport size
// to ensure the view position remains aligned on a tab
// boundary
Dimension extentSize = new Dimension(viewSize.width
- tabViewPosition.x, viewRect.height);
viewport.setExtentSize(extentSize);
}
break;
case LEFT:
case RIGHT:
tabViewPosition.y = leadingTabIndex == 0 ? 0
: rects[leadingTabIndex].y;
if (viewSize.height - tabViewPosition.y < viewRect.height) {
// We've scrolled to the end, so adjust the viewport size
// to ensure the view position remains aligned on a tab
// boundary
Dimension extentSize = new Dimension(viewRect.width,
viewSize.height - tabViewPosition.y);
viewport.setExtentSize(extentSize);
}
}
viewport.setViewPosition(tabViewPosition);
}
@Override
public void stateChanged(ChangeEvent e) {
JViewport viewport = (JViewport) e.getSource();
int tabPlacement = tabPane.getTabPlacement();
int tabCount = tabPane.getTabCount();
Rectangle vpRect = viewport.getBounds();
Dimension viewSize = viewport.getViewSize();
Rectangle viewRect = viewport.getViewRect();
leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
// If the tab isn't right aligned, adjust it.
if (leadingTabIndex + 1 < tabCount) {
switch (tabPlacement) {
case TOP:
case BOTTOM:
if (rects[leadingTabIndex].x < viewRect.x) {
leadingTabIndex++;
}
break;
case LEFT:
case RIGHT:
if (rects[leadingTabIndex].y < viewRect.y) {
leadingTabIndex++;
}
break;
}
}
Insets contentInsets = getContentBorderInsets(tabPlacement);
switch (tabPlacement) {
case LEFT:
tabPane.repaint(vpRect.x + vpRect.width, vpRect.y,
contentInsets.left, vpRect.height);
scrollBackwardButton.setEnabled(viewRect.y > 0
&& leadingTabIndex > 0);
scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1
&& viewSize.height - viewRect.y > viewRect.height);
break;
case RIGHT:
tabPane.repaint(vpRect.x - contentInsets.right, vpRect.y,
contentInsets.right, vpRect.height);
scrollBackwardButton.setEnabled(viewRect.y > 0
&& leadingTabIndex > 0);
scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1
&& viewSize.height - viewRect.y > viewRect.height);
break;
case BOTTOM:
tabPane.repaint(vpRect.x, vpRect.y - contentInsets.bottom,
vpRect.width, contentInsets.bottom);
scrollBackwardButton.setEnabled(viewRect.x > 0
&& leadingTabIndex > 0);
scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1
&& viewSize.width - viewRect.x > viewRect.width);
break;
case TOP:
default:
tabPane.repaint(vpRect.x, vpRect.y + vpRect.height,
vpRect.width, contentInsets.top);
scrollBackwardButton.setEnabled(viewRect.x > 0
&& leadingTabIndex > 0);
scrollForwardButton.setEnabled(leadingTabIndex < tabCount - 1
&& viewSize.width - viewRect.x > viewRect.width);
}
}
/**
* ActionListener for the scroll buttons.
*/
@Override
public void actionPerformed(ActionEvent e) {
ActionMap map = tabPane.getActionMap();
if (map != null) {
String actionKey;
if (e.getSource() == scrollForwardButton) {
actionKey = "scrollTabsForwardAction";
} else {
actionKey = "scrollTabsBackwardAction";
}
Action action = map.get(actionKey);
if (action != null && action.isEnabled()) {
action.actionPerformed(new ActionEvent(tabPane,
ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e
.getModifiers()));
}
}
}
}
private final class ScrollableTabViewport extends JViewport implements UIResource {
public ScrollableTabViewport() {
super();
setName("TabbedPane.scrollableViewport");
setScrollMode(SIMPLE_SCROLL_MODE);
setOpaque(tabPane.isOpaque());
Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
if (bgColor == null) {
bgColor = tabPane.getBackground();
}
setBackground(bgColor);
}
}
private final class ScrollableTabPanel extends JPanel implements UIResource {
public ScrollableTabPanel() {
super(null);
setOpaque(tabPane.isOpaque());
Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
if (bgColor == null) {
bgColor = tabPane.getBackground();
}
setBackground(bgColor);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
PlasticTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(),
tabPane.getSelectedIndex());
}
}
private static final class ArrowButton extends JButton implements UIResource {
private final int buttonWidth;
private final int direction;
private boolean mouseIsOver;
ArrowButton(int direction, int buttonWidth) {
this.direction = direction;
this.buttonWidth = buttonWidth;
setRequestFocusEnabled(false);
}
@Override
protected void processMouseEvent(MouseEvent e) {
super.processMouseEvent(e);
switch (e.getID()) {
case MouseEvent.MOUSE_ENTERED:
mouseIsOver = true;
revalidate();
repaint();
break;
case MouseEvent.MOUSE_EXITED:
mouseIsOver = false;
revalidate();
repaint();
break;
}
}
@Override
protected void paintBorder(Graphics g) {
if (mouseIsOver && isEnabled()) {
super.paintBorder(g);
}
}
@Override
protected void paintComponent(Graphics g) {
if (mouseIsOver) {
super.paintComponent(g);
} else {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
paintArrow(g);
}
private void paintArrow(Graphics g) {
Color oldColor = g.getColor();
boolean isEnabled = isEnabled();
g.setColor(isEnabled ? MetalLookAndFeel.getControlInfo()
: MetalLookAndFeel.getControlDisabled());
int arrowWidth, arrowHeight;
switch (direction) {
case NORTH:
case SOUTH:
arrowWidth = 9;
arrowHeight = 5;
break;
case WEST:
case EAST:
default:
arrowWidth = 5;
arrowHeight = 9;
break;
}
int x = (getWidth() - arrowWidth ) / 2;
int y = (getHeight() - arrowHeight) / 2;
g.translate(x, y);
boolean paintShadow = !mouseIsOver || !isEnabled;
Color shadow = isEnabled ? MetalLookAndFeel.getControlShadow()
: UIManager.getColor("ScrollBar.highlight");
switch (direction) {
case NORTH:
g.fillRect(0, 4, 9, 1);
g.fillRect(1, 3, 7, 1);
g.fillRect(2, 2, 5, 1);
g.fillRect(3, 1, 3, 1);
g.fillRect(4, 0, 1, 1);
if (paintShadow) {
g.setColor(shadow);
g.fillRect(1, 5, 9, 1);
}
break;
case SOUTH:
g.fillRect(0, 0, 9, 1);
g.fillRect(1, 1, 7, 1);
g.fillRect(2, 2, 5, 1);
g.fillRect(3, 3, 3, 1);
g.fillRect(4, 4, 1, 1);
if (paintShadow) {
g.setColor(shadow);
g.drawLine(5, 4, 8, 1);
g.drawLine(5, 5, 9, 1);
}
break;
case WEST:
g.fillRect(0, 4, 1, 1);
g.fillRect(1, 3, 1, 3);
g.fillRect(2, 2, 1, 5);
g.fillRect(3, 1, 1, 7);
g.fillRect(4, 0, 1, 9);
if (paintShadow) {
g.setColor(shadow);
g.fillRect(5, 1, 1, 9);
}
break;
case EAST:
g.fillRect(0, 0, 1, 9);
g.fillRect(1, 1, 1, 7);
g.fillRect(2, 2, 1, 5);
g.fillRect(3, 3, 1, 3);
g.fillRect(4, 4, 1, 1);
if (paintShadow) {
g.setColor(shadow);
g.drawLine(1, 8, 4, 5);
g.drawLine(1, 9, 5, 5);
}
break;
}
g.translate(-x, -y);
g.setColor(oldColor);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(buttonWidth, buttonWidth);
}
@Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
@Override
public Dimension getMaximumSize() {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
}
/**
* This is the abstract superclass for all TabbedPane renderers.
* Those will be defined in the rest of this file
*/
private abstract static class AbstractRenderer {
protected static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
protected static final Insets NORTH_INSETS = new Insets(1, 0, 0, 0);
protected static final Insets WEST_INSETS = new Insets(0, 1, 0, 0);
protected static final Insets SOUTH_INSETS = new Insets(0, 0, 1, 0);
protected static final Insets EAST_INSETS = new Insets(0, 0, 0, 1);
protected final JTabbedPane tabPane;
protected final int tabPlacement;
protected Color shadowColor;
protected Color darkShadow;
protected Color selectColor;
protected Color selectLight;
protected Color selectHighlight;
protected Color lightHighlight;
protected Color focus;
private AbstractRenderer(JTabbedPane tabPane) {
initColors();
this.tabPane = tabPane;
this.tabPlacement = tabPane.getTabPlacement();
}
private static AbstractRenderer createRenderer(JTabbedPane tabPane) {
switch (tabPane.getTabPlacement()) {
case SwingConstants.TOP :
return new TopRenderer(tabPane);
case SwingConstants.BOTTOM :
return new BottomRenderer(tabPane);
case SwingConstants.LEFT :
return new LeftRenderer(tabPane);
case SwingConstants.RIGHT :
return new RightRenderer(tabPane);
default :
return new TopRenderer(tabPane);
}
}
private static AbstractRenderer createEmbeddedRenderer(JTabbedPane tabPane) {
switch (tabPane.getTabPlacement()) {
case SwingConstants.TOP :
return new TopEmbeddedRenderer(tabPane);
case SwingConstants.BOTTOM :
return new BottomEmbeddedRenderer(tabPane);
case SwingConstants.LEFT :
return new LeftEmbeddedRenderer(tabPane);
case SwingConstants.RIGHT :
return new RightEmbeddedRenderer(tabPane);
default :
return new TopEmbeddedRenderer(tabPane);
}
}
private void initColors() {
shadowColor = UIManager.getColor("TabbedPane.shadow");
darkShadow = UIManager.getColor("TabbedPane.darkShadow");
selectColor = UIManager.getColor("TabbedPane.selected");
focus = UIManager.getColor("TabbedPane.focus");
selectHighlight = UIManager.getColor("TabbedPane.selectHighlight");
lightHighlight = UIManager.getColor("TabbedPane.highlight");
selectLight =
new Color(
(2 * selectColor.getRed() + selectHighlight.getRed()) / 3,
(2 * selectColor.getGreen() + selectHighlight.getGreen()) / 3,
(2 * selectColor.getBlue() + selectHighlight.getBlue()) / 3);
}
protected boolean isFirstDisplayedTab(int tabIndex, int position, int paneBorder) {
return tabIndex == 0;
// return (position - paneBorder) < 8;
}
protected Insets getTabAreaInsets(Insets defaultInsets) {
return defaultInsets;
}
protected Insets getContentBorderInsets(Insets defaultInsets) {
return defaultInsets;
}
/**
* Returns the amount by which the label should be shifted horizontally.
*/
protected int getTabLabelShiftX(int tabIndex, boolean isSelected) {
return 0;
}
/**
* Returns the amount by which the label should be shifted vertically.
*/
protected int getTabLabelShiftY(int tabIndex, boolean isSelected) {
return 0;
}
/**
* Returns the amount of overlap for two Runs.
*/
protected int getTabRunOverlay(int tabRunOverlay) {
return tabRunOverlay;
}
/**
* Returns if a run should be padded with empty space
* to take up as much room as the others.
*/
protected boolean shouldPadTabRun(int run, boolean aPriori) {
return aPriori;
}
/**
* Returns the amount by which the run number {@code run}
* should be indented. Add a few pixels for every run to make
* diagonal lines align.
*/
protected int getTabRunIndent(int run) {
return 0;
}
/**
* Returns the insets for the given tab.
*/
protected abstract Insets getTabInsets(int tabIndex, Insets tabInsets);
/**
* Draws the rectancle around the Tab label which indicates keyboard focus.
*/
protected abstract void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected);
/**
* Fills the background of the given tab to make sure overlap of
* tabs is handled correctly.
*/
protected abstract void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected);
/**
* Paints the border around the given tab.
*/
protected abstract void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected);
/**
* Returns additional the insets for the selected tab. This allows to "raise"
* The selected tab over the others
*/
protected Insets getSelectedTabPadInsets() {
return EMPTY_INSETS;
}
/**
* Draws the top edge of the border around the content area.
* Draw unbroken line for tabs are not on TOP
* override where appropriate.
*/
protected void paintContentBorderTopEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
if (isContentBorderPainted) {
g.setColor(selectHighlight);
g.fillRect(x, y, w - 1, 1);
}
}
/**
* Draws the bottom edge of the Border around the content area.
* Draw broken line if selected tab is visible and adjacent to content
* and TabPlacement is same as painted edge.
*/
protected void paintContentBorderBottomEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
if (isContentBorderPainted) {
g.setColor(darkShadow);
g.fillRect(x, y + h - 1, w - 1, 1);
}
}
/**
* Draws the left edge of the Border around the content area.
* Draw broken line if selected tab is visible and adjacent to content
* and TabPlacement is same as painted edge
*/
protected void paintContentBorderLeftEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
if (isContentBorderPainted) {
g.setColor(selectHighlight);
g.fillRect(x, y, 1, h - 1);
}
}
/**
* Draws the right edge of the Border around the content area.
* Draw broken line if selected tab is visible and adjacent to content
* and TabPlacement is same as painted edge
*/
protected void paintContentBorderRightEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
if (isContentBorderPainted) {
g.setColor(darkShadow);
g.fillRect(x + w - 1, y, 1, h);
}
}
/**
* Returns the amount of overlap for two tabs.
*/
protected int getTabsOverlay() {
return 0;
}
}
/**
* The renderer for the case where tabs are displayed below the contents
* and with minimal decoration.
*/
private static final class BottomEmbeddedRenderer extends AbstractRenderer {
private BottomEmbeddedRenderer(JTabbedPane tabPane) {
super(tabPane);
}
@Override
protected Insets getTabAreaInsets(Insets insets) {
return EMPTY_INSETS;
}
@Override
protected Insets getContentBorderInsets(Insets defaultInsets) {
return SOUTH_INSETS;
}
@Override
protected Insets getSelectedTabPadInsets() {
return EMPTY_INSETS;
}
@Override
protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
return new Insets(tabInsets.top, tabInsets.left, tabInsets.bottom, tabInsets.right);
}
/**
* Paints no focus: minimal decoration is really minimal.
*/
@Override
protected void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
// Embedded tabs paint no focus.
}
@Override
protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
g.setColor(selectColor);
g.fillRect(x, y, w + 1, h);
}
@Override
protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
int bottom = h;
int right = w + 1;
g.translate(x, y);
if (isFirstDisplayedTab(tabIndex, x, tabPane.getBounds().x)) {
if (isSelected) {
// selected and first in line
g.setColor(shadowColor);
g.fillRect(right, 0, 1, bottom - 1);
g.fillRect(right - 1, bottom - 1, 1, 1);
// it is open to discussion if the outer border of the tab
// should be painted because in the primary case it won't
// be visible anyway. uncomment the following two lines if wanted
// g.fillRect(0,bottom, right, 1);
// g.fillRect(-1,0,1,bottom;
g.setColor(selectHighlight);
g.fillRect(0, 0, 1, bottom);
g.fillRect(right - 1, 0, 1, bottom - 1);
g.fillRect(1, bottom - 1, right - 2, 1);
} else {
//not selected and first in line
}
} else {
if (isSelected) {
//selected and not first in line
g.setColor(shadowColor);
g.fillRect(0, 0, 1, bottom - 1);
g.fillRect(1, bottom - 1, 1, 1);
g.fillRect(right, 0, 1, bottom - 1);
g.fillRect(right - 1, bottom - 1, 1, 1);
// outside line:
// g.fillRect(2,bottom, right-3, 1);
g.setColor(selectHighlight);
g.fillRect(1, 0, 1, bottom - 1);
g.fillRect(right - 1, 0, 1, bottom - 1);
g.fillRect(2, bottom - 1, right - 3, 1);
} else {
g.setColor(shadowColor);
g.fillRect(1, h / 2, 1, h - h / 2);
}
}
g.translate(-x, -y);
}
@Override
protected void paintContentBorderBottomEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
g.setColor(shadowColor);
g.fillRect(x, y + h - 1, w, 1);
}
}
/**
* The renderer for the case where Tabs are below the content and
* decoration is standard.
*/
private static final class BottomRenderer extends AbstractRenderer {
private BottomRenderer(JTabbedPane tabPane) {
super(tabPane);
}
@Override
protected Insets getTabAreaInsets(Insets defaultInsets) {
return new Insets(defaultInsets.top, defaultInsets.left + 5, defaultInsets.bottom, defaultInsets.right);
}
@Override
protected int getTabLabelShiftY(int tabIndex, boolean isSelected) {
return isSelected ? 0 : -1;
}
@Override
protected int getTabRunOverlay(int tabRunOverlay) {
return tabRunOverlay - 2;
}
@Override
protected int getTabRunIndent(int run) {
return 6 * run;
}
@Override
protected Insets getSelectedTabPadInsets() {
return SOUTH_INSETS;
}
@Override
protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
return new Insets(tabInsets.top, tabInsets.left - 2, tabInsets.bottom, tabInsets.right - 2);
}
@Override
protected void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
if (!tabPane.hasFocus() || !isSelected) {
return;
}
Rectangle tabRect = rects[tabIndex];
int top = tabRect.y;
int left = tabRect.x + 6;
int height = tabRect.height - 3;
int width = tabRect.width - 12;
g.setColor(focus);
g.drawRect(left, top, width, height);
}
@Override
protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
g.setColor(selectColor);
g.fillRect(x, y, w, h);
}
@Override
protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
int bottom = h - 1;
int right = w + 4;
g.translate(x - 3, y);
// Paint Border
g.setColor(selectHighlight);
// Paint left
g.fillRect(0, 0, 1, 2);
g.drawLine(0, 2, 4, bottom - 4);
g.fillRect(5, bottom - 3, 1, 2);
g.fillRect(6, bottom - 1, 1, 1);
// Paint bootom
g.fillRect(7, bottom, 1, 1);
g.setColor(darkShadow);
g.fillRect(8, bottom, right - 13, 1);
// Paint right
g.drawLine(right + 1, 0, right - 3, bottom - 4);
g.fillRect(right - 4, bottom - 3, 1, 2);
g.fillRect(right - 5, bottom - 1, 1, 1);
g.translate(-x + 3, -y);
}
@Override
protected void paintContentBorderBottomEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
int bottom = y + h - 1;
int right = x + w - 1;
g.translate(x, bottom);
if (drawBroken && selRect.x >= x && selRect.x <= x + w) {
// Break line to show visual connection to selected tab
g.setColor(darkShadow);
g.fillRect(0, 0, selRect.x - x - 2, 1);
if (selRect.x + selRect.width < x + w - 2) {
g.setColor(darkShadow);
g.fillRect(selRect.x + selRect.width + 2 - x, 0, right - selRect.x - selRect.width - 2, 1);
}
} else {
g.setColor(darkShadow);
g.fillRect(0, 0, w - 1, 1);
}
g.translate(-x, -bottom);
}
@Override
protected int getTabsOverlay() {
return 4;
}
}
/**
* The renderer for tabs on the left with minimal decoration.
*/
private static final class LeftEmbeddedRenderer extends AbstractRenderer {
private LeftEmbeddedRenderer(JTabbedPane tabPane) {
super(tabPane);
}
@Override
protected Insets getTabAreaInsets(Insets insets) {
return EMPTY_INSETS;
}
@Override
protected Insets getContentBorderInsets(Insets defaultInsets) {
return WEST_INSETS;
}
@Override
protected int getTabRunOverlay(int tabRunOverlay) {
return 0;
}
@Override
protected boolean shouldPadTabRun(int run, boolean aPriori) {
return false;
}
@Override
protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
return new Insets(tabInsets.top, tabInsets.left, tabInsets.bottom, tabInsets.right);
}
@Override
protected Insets getSelectedTabPadInsets() {
return EMPTY_INSETS;
}
/**
* minimal decoration is really minimal: no focus.
*/
@Override
protected void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
// Embedded tabs paint no focus.
}
@Override
protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
g.setColor(selectColor);
g.fillRect(x, y, w, h);
}
@Override
protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
int bottom = h;
int right = w;
g.translate(x, y);
if (isFirstDisplayedTab(tabIndex, y, tabPane.getBounds().y)) {
if (isSelected) {
//selected and first in line
g.setColor(selectHighlight);
g.fillRect(0, 0, right, 1);
g.fillRect(0, 0, 1, bottom - 1);
g.fillRect(1, bottom - 1, right - 1, 1);
g.setColor(shadowColor);
g.fillRect(0, bottom - 1, 1, 1);
g.fillRect(1, bottom, right - 1, 1);
// outside line:
// g.fillRect(-1,0,1,bottom-1)
} else {
//not selected but first in line
}
} else {
if (isSelected) {
//selected but not first in line
g.setColor(selectHighlight);
g.fillRect(1, 1, right - 1, 1);
g.fillRect(0, 2, 1, bottom - 2);
g.fillRect(1, bottom - 1, right - 1, 1);
g.setColor(shadowColor);
g.fillRect(1, 0, right - 1, 1);
g.fillRect(0, 1, 1, 1);
g.fillRect(0, bottom - 1, 1, 1);
g.fillRect(1, bottom, right - 1, 1);
// outside line:
// g.fillRect(-1,2,1,bottom-3)
} else {
g.setColor(shadowColor);
g.fillRect(0, 0, right / 3, 1);
}
}
g.translate(-x, -y);
}
@Override
protected void paintContentBorderLeftEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
g.setColor(shadowColor);
g.fillRect(x, y, 1, h);
}
}
/**
* Renderer for tabs on the left with normal decoration.
*/
private static final class LeftRenderer extends AbstractRenderer {
private LeftRenderer(JTabbedPane tabPane) {
super(tabPane);
}
@Override
protected Insets getTabAreaInsets(Insets defaultInsets) {
return new Insets(defaultInsets.top + 4, defaultInsets.left, defaultInsets.bottom, defaultInsets.right);
}
@Override
protected int getTabLabelShiftX(int tabIndex, boolean isSelected) {
return 1;
}
@Override
protected int getTabRunOverlay(int tabRunOverlay) {
return 1;
}
@Override
protected boolean shouldPadTabRun(int run, boolean aPriori) {
return false;
}
@Override
protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
return new Insets(tabInsets.top, tabInsets.left - 5, tabInsets.bottom + 1, tabInsets.right - 5);
}
@Override
protected Insets getSelectedTabPadInsets() {
return WEST_INSETS;
}
@Override
protected void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
if (!tabPane.hasFocus() || !isSelected) {
return;
}
Rectangle tabRect = rects[tabIndex];
int top = tabRect.y + 2;
int left = tabRect.x + 3;
int height = tabRect.height - 5;
int width = tabRect.width - 6;
g.setColor(focus);
g.drawRect(left, top, width, height);
}
@Override
protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
if (!isSelected) {
g.setColor(selectLight);
g.fillRect(x + 1, y + 1, w - 1, h - 2);
} else {
g.setColor(selectColor);
g.fillRect(x + 1, y + 1, w - 3, h - 2);
}
}
@Override
protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
int bottom = h - 1;
int left = 0;
g.translate(x, y);
// Paint Border
g.setColor(selectHighlight);
// Paint top
g.fillRect(left + 2, 0, w - 2 - left, 1);
// Paint left
g.fillRect(left + 1, 1, 1, 1);
g.fillRect(left, 2, 1, bottom - 3);
g.setColor(darkShadow);
g.fillRect(left + 1, bottom - 1, 1, 1);
// Paint bottom
g.fillRect(left + 2, bottom, w - 2 - left, 1);
g.translate(-x, -y);
}
@Override
protected void paintContentBorderLeftEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
g.setColor(selectHighlight);
if (drawBroken && selRect.y >= y && selRect.y <= y + h) {
// Break line to show visual connection to selected tab
g.fillRect(x, y, 1, selRect.y + 1 - y);
if (selRect.y + selRect.height < y + h - 2) {
g.fillRect(x, selRect.y + selRect.height - 1, 1, y + h - selRect.y - selRect.height);
}
} else {
g.fillRect(x, y, 1, h - 1);
}
}
}
/**
* The renderer for tabs on the right with minimal decoration.
*/
private static final class RightEmbeddedRenderer extends AbstractRenderer {
private RightEmbeddedRenderer(JTabbedPane tabPane) {
super(tabPane);
}
@Override
protected Insets getTabAreaInsets(Insets insets) {
return EMPTY_INSETS;
}
@Override
protected Insets getContentBorderInsets(Insets defaultInsets) {
return EAST_INSETS;
}
@Override
protected int getTabRunIndent(int run) {
return 4 * run;
}
@Override
protected int getTabRunOverlay(int tabRunOverlay) {
return 0;
}
@Override
protected boolean shouldPadTabRun(int run, boolean aPriori) {
return false;
}
@Override
protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
return new Insets(tabInsets.top, tabInsets.left, tabInsets.bottom, tabInsets.right);
}
@Override
protected Insets getSelectedTabPadInsets() {
return EMPTY_INSETS;
}
/**
* Minimal decoration: no focus.
*/
@Override
protected void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
// Embedded tabs paint no focus.
}
@Override
protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
g.setColor(selectColor);
g.fillRect(x, y, w, h);
}
@Override
protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
int bottom = h;
int right = w - 1;
g.translate(x + 1, y);
if (isFirstDisplayedTab(tabIndex, y, tabPane.getBounds().y)) {
if (isSelected) {
//selected and first in line
g.setColor(shadowColor);
//outside lines:
// g.fillRect(0,-1,right,1);
// g.fillRect(right,-1,1,bottom);
g.fillRect(right - 1, bottom - 1, 1, 1);
g.fillRect(0, bottom, right - 1, 1);
g.setColor(selectHighlight);
g.fillRect(0, 0, right - 1, 1);
g.fillRect(right - 1, 0, 1, bottom - 1);
g.fillRect(0, bottom - 1, right - 1, 1);
}
} else {
if (isSelected) {
//selected but not first in line
g.setColor(shadowColor);
g.fillRect(0, -1, right - 1, 1);
g.fillRect(right - 1, 0, 1, 1);
//outside line:
// g.fillRect(right,0,1,bottom);
g.fillRect(right - 1, bottom - 1, 1, 1);
g.fillRect(0, bottom, right - 1, 1);
g.setColor(selectHighlight);
g.fillRect(0, 0, right - 1, 1);
g.fillRect(right - 1, 1, 1, bottom - 2);
g.fillRect(0, bottom - 1, right - 1, 1);
} else {
//not selected and not first in line
g.setColor(shadowColor);
g.fillRect(2 * right / 3, 0, right / 3, 1);
}
}
g.translate(-x - 1, -y);
}
@Override
protected void paintContentBorderRightEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
g.setColor(shadowColor);
g.fillRect(x + w - 1, y, 1, h);
}
}
/**
* Renderer for tabs on the right with normal decoration.
*/
private static final class RightRenderer extends AbstractRenderer {
private RightRenderer(JTabbedPane tabPane) {
super(tabPane);
}
@Override
protected int getTabLabelShiftX(int tabIndex, boolean isSelected) {
return 1;
}
@Override
protected int getTabRunOverlay(int tabRunOverlay) {
return 1;
}
@Override
protected boolean shouldPadTabRun(int run, boolean aPriori) {
return false;
}
@Override
protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
return new Insets(tabInsets.top, tabInsets.left - 5, tabInsets.bottom + 1, tabInsets.right - 5);
}
@Override
protected Insets getSelectedTabPadInsets() {
return EAST_INSETS;
}
@Override
protected void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
if (!tabPane.hasFocus() || !isSelected) {
return;
}
Rectangle tabRect = rects[tabIndex];
int top = tabRect.y + 2;
int left = tabRect.x + 3;
int height = tabRect.height - 5;
int width = tabRect.width - 6;
g.setColor(focus);
g.drawRect(left, top, width, height);
}
@Override
protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
if (!isSelected) {
g.setColor(selectLight);
g.fillRect(x, y, w, h);
} else {
g.setColor(selectColor);
g.fillRect(x + 2, y, w - 2, h);
}
}
@Override
protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
int bottom = h - 1;
int right = w;
g.translate(x, y);
// Paint Border
g.setColor(selectHighlight);
g.fillRect(0, 0, right - 1, 1);
// Paint right
g.setColor(darkShadow);
g.fillRect(right - 1, 1, 1, 1);
g.fillRect(right, 2, 1, bottom - 3);
// Paint bottom
g.fillRect(right - 1, bottom - 1, 1, 1);
g.fillRect(0, bottom, right - 1, 1);
g.translate(-x, -y);
}
@Override
protected void paintContentBorderRightEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
g.setColor(darkShadow);
if (drawBroken && selRect.y >= y && selRect.y <= y + h) {
// Break line to show visual connection to selected tab
g.fillRect(x + w - 1, y, 1, selRect.y - y);
if (selRect.y + selRect.height < y + h - 2) {
g.fillRect(x + w - 1, selRect.y + selRect.height, 1, y + h - selRect.y - selRect.height);
}
} else {
g.fillRect(x + w - 1, y, 1, h - 1);
}
}
}
/**
* Renderer for tabs on top with minimal decoration.
*/
private static final class TopEmbeddedRenderer extends AbstractRenderer {
private TopEmbeddedRenderer(JTabbedPane tabPane) {
super(tabPane);
}
@Override
protected Insets getTabAreaInsets(Insets insets) {
return EMPTY_INSETS;
}
@Override
protected Insets getContentBorderInsets(Insets defaultInsets) {
return NORTH_INSETS;
}
@Override
protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
return new Insets(tabInsets.top, tabInsets.left + 1, tabInsets.bottom, tabInsets.right);
}
@Override
protected Insets getSelectedTabPadInsets() {
return EMPTY_INSETS;
}
/**
* Minimal decoration: no focus.
*/
@Override
protected void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
// Embedded tabs paint no focus.
}
@Override
protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
g.setColor(selectColor);
g.fillRect(x, y, w, h);
}
@Override
protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
g.translate(x, y);
int right = w;
int bottom = h;
if (isFirstDisplayedTab(tabIndex, x, tabPane.getBounds().x)) {
if (isSelected) {
g.setColor(selectHighlight);
//left
g.fillRect(0, 0, 1, bottom);
//top
g.fillRect(0, 0, right - 1, 1);
//right
g.fillRect(right - 1, 0, 1, bottom);
g.setColor(shadowColor);
//topright corner
g.fillRect(right - 1, 0, 1, 1);
//right
g.fillRect(right, 1, 1, bottom);
}
} else {
if (isSelected) {
g.setColor(selectHighlight);
//left
g.fillRect(1, 1, 1, bottom - 1);
//top
g.fillRect(2, 0, right - 3, 1);
//right
g.fillRect(right - 1, 1, 1, bottom - 1);
g.setColor(shadowColor);
//left
g.fillRect(0, 1, 1, bottom - 1);
//topleft corner
g.fillRect(1, 0, 1, 1);
//topright corner
g.fillRect(right - 1, 0, 1, 1);
//right
g.fillRect(right, 1, 1, bottom);
} else {
g.setColor(shadowColor);
g.fillRect(0, 0, 1, bottom +2 - bottom / 2);
}
}
g.translate(-x, -y);
}
@Override
protected void paintContentBorderTopEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
g.setColor(shadowColor);
g.fillRect(x, y, w, 1);
}
}
/**
* Renderer for tabs on top with normal decoration.
*/
private static final class TopRenderer extends AbstractRenderer {
private TopRenderer(JTabbedPane tabPane) {
super(tabPane);
}
@Override
protected Insets getTabAreaInsets(Insets defaultInsets) {
return new Insets(defaultInsets.top, defaultInsets.left + 4, defaultInsets.bottom, defaultInsets.right);
}
@Override
protected int getTabLabelShiftY(int tabIndex, boolean isSelected) {
return isSelected ? -1 : 0;
}
@Override
protected int getTabRunOverlay(int tabRunOverlay) {
return tabRunOverlay - 2;
}
@Override
protected int getTabRunIndent(int run) {
return 6 * run;
}
@Override
protected Insets getSelectedTabPadInsets() {
return NORTH_INSETS;
}
@Override
protected Insets getTabInsets(int tabIndex, Insets tabInsets) {
return new Insets(tabInsets.top-1, tabInsets.left - 4, tabInsets.bottom, tabInsets.right - 4);
}
@Override
protected void paintFocusIndicator(
Graphics g,
Rectangle[] rects,
int tabIndex,
Rectangle iconRect,
Rectangle textRect,
boolean isSelected) {
if (!tabPane.hasFocus() || !isSelected) {
return;
}
Rectangle tabRect = rects[tabIndex];
int top = tabRect.y +1 ;
int left = tabRect.x + 4;
int height = tabRect.height - 3;
int width = tabRect.width - 9;
g.setColor(focus);
g.drawRect(left, top, width, height);
}
@Override
protected void paintTabBackground(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
int sel = isSelected ? 0 : 1;
g.setColor(selectColor);
g.fillRect(x, y + sel, w, h / 2);
g.fillRect(x - 1, y + sel + h / 2, w + 2, h - h / 2);
}
@Override
protected void paintTabBorder(Graphics g, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
g.translate(x - 4, y);
int top = 0;
int right = w + 6;
// Paint Border
g.setColor(selectHighlight);
// Paint left
g.drawLine(1, h - 1, 4, top + 4);
g.fillRect(5, top + 2, 1, 2);
g.fillRect(6, top + 1, 1, 1);
// Paint top
g.fillRect(7, top, right - 12, 1);
// Paint right
g.setColor(darkShadow);
g.drawLine(right, h - 1, right - 3, top + 4);
g.fillRect(right - 4, top + 2, 1, 2);
g.fillRect(right - 5, top + 1, 1, 1);
g.translate(-x + 4, -y);
}
@Override
protected void paintContentBorderTopEdge(
Graphics g,
int x,
int y,
int w,
int h,
boolean drawBroken,
Rectangle selRect,
boolean isContentBorderPainted) {
int right = x + w - 1;
int top = y;
g.setColor(selectHighlight);
if (drawBroken && selRect.x >= x && selRect.x <= x + w) {
// Break line to show visual connection to selected tab
g.fillRect(x, top, selRect.x - 2 - x, 1);
if (selRect.x + selRect.width < x + w - 2) {
g.fillRect(selRect.x + selRect.width + 2, top, right - 2 - selRect.x - selRect.width, 1);
} else {
g.fillRect(x + w - 2, top, 1, 1);
}
} else {
g.fillRect(x, top, w - 1, 1);
}
}
@Override
protected int getTabsOverlay() {
return 6;
}
}
}