/*
* @(#)PaletteTabbedPaneUI.java
*
* Copyright (c) 2010 The authors and contributors of JHotDraw.
*
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.gui.plaf.palette;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LinearGradientPaint;
import java.awt.MultipleGradientPaint;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.plaf.metal.MetalTabbedPaneUI;
import javax.swing.text.View;
/**
* PaletteTabbedPaneUI.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class PaletteTabbedPaneUI extends BasicTabbedPaneUI {
private static final float[] enabledStops = new float[]{0f, 0.35f, 0.4f, 1f};
private static final Color[] enabledStopColors = new Color[]{new Color(0xf8f8f8), new Color(0xeeeeee), new Color(0xcacaca), new Color(0xffffff)};
private boolean tabsOverlapBorder;
private Color selectedColor;
private boolean tabsOpaque = true;
private boolean contentOpaque = true;
public static ComponentUI createUI(JComponent c) {
return new PaletteTabbedPaneUI();
}
@Override
protected void installDefaults() {
PaletteLookAndFeel laf = PaletteLookAndFeel.getInstance();
MetalTabbedPaneUI mtpui;
PaletteLookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
"TabbedPane.foreground", "TabbedPane.font");
highlight = laf.getColor("TabbedPane.light");
lightHighlight = laf.getColor("TabbedPane.highlight");
shadow = laf.getColor("TabbedPane.shadow");
darkShadow = laf.getColor("TabbedPane.darkShadow");
focus = laf.getColor("TabbedPane.focus");
selectedColor = laf.getColor("TabbedPane.selected"); // will probably not
// work as expected since we don't override enough colors from BasicTabbedPaneUI
textIconGap = laf.getInt("TabbedPane.textIconGap");
tabInsets = laf.getInsets("TabbedPane.tabInsets");
selectedTabPadInsets = laf.getInsets("TabbedPane.selectedTabPadInsets");
tabAreaInsets = laf.getInsets("TabbedPane.tabAreaInsets");
tabsOverlapBorder = laf.getBoolean("TabbedPane.tabsOverlapBorder");
; // will probably not
// work as expected since we don't override enough colors from BasicTabbedPaneUI
contentBorderInsets = laf.getInsets("TabbedPane.contentBorderInsets");
tabRunOverlay = laf.getInt("TabbedPane.tabRunOverlay");
tabsOpaque = laf.getBoolean("TabbedPane.tabsOpaque");// will probably not
// work as expected since we don't override enough colors from BasicTabbedPaneUI
contentOpaque = laf.getBoolean("TabbedPane.contentOpaque");// will probably not
// work as expected since we don't override enough colors from BasicTabbedPaneUI
Object opaque = laf.get("TabbedPane.opaque");
if (opaque == null) {
opaque = Boolean.FALSE;
}
PaletteLookAndFeel.installProperty(tabPane, "opaque", opaque);
// Fix for 6711145 BasicTabbedPanuUI should not throw a NPE if these
// keys are missing. So we are setting them to there default values here
// if the keys are missing.
if (tabInsets == null) {
tabInsets = new Insets(0, 4, 1, 4);
}
if (selectedTabPadInsets == null) {
selectedTabPadInsets = new Insets(2, 2, 2, 1);
}
if (tabAreaInsets == null) {
tabAreaInsets = new Insets(3, 2, 0, 2);
}
if (contentBorderInsets == null) {
contentBorderInsets = new Insets(2, 2, 3, 3);
}
}
@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;
if (tabsOpaque || tabPane.isOpaque()) {
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 = isSelected ? PaletteLookAndFeel.getInstance().getFont("TabbedPane.selectedFont") : tabPane.getFont();
FontMetrics metrics = PaletteUtilities.getFontMetrics(tabPane, g, font);
Icon icon = getIconForTab(tabIndex);
layoutLabel(tabPlacement, metrics, tabIndex, title, icon,
tabRect, iconRect, textRect, isSelected);
if (tabPane.getTabComponentAt(tabIndex) == null) {
String clippedTitle = title;
// XXX - Implement scrollable tab layout support.
/*
if (scrollableTabLayoutEnabled() && tabScroller.croppedEdge.isParamsSet() &&
tabScroller.croppedEdge.getTabIndex() == tabIndex && isHorizontalTabPlacement()) {
int availTextWidth = tabScroller.croppedEdge.getCropline() -
(textRect.x - tabRect.x) - tabScroller.croppedEdge.getCroppedSideWidth();
clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, availTextWidth);
}*/
paintText(g, tabPlacement, font, metrics,
tabIndex, clippedTitle, textRect, isSelected);
paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
}
paintFocusIndicator(g, tabPlacement, rects, tabIndex,
iconRect, textRect, isSelected);
}
/**
* this function draws the border around each tab
* note that this function does now draw the background of the tab.
* that is done elsewhere
*/
@Override
protected void paintTabBorder(Graphics g, int tabPlacement,
int tabIndex,
int x, int y, int w, int h,
boolean isSelected) {
g.setColor(isSelected ? darkShadow : shadow);
switch (tabPlacement) {
case LEFT:
g.drawLine(x + 1, y + h - 2, x + 1, y + h - 2); // bottom-left highlight
g.drawLine(x, y + 2, x, y + h - 3); // left highlight
g.drawLine(x + 1, y + 1, x + 1, y + 1); // top-left highlight
g.drawLine(x + 2, y, x + w - 1, y); // top highlight
g.drawLine(x + 2, y + h - 2, x + w - 1, y + h - 2); // bottom shadow
g.drawLine(x + 2, y + h - 1, x + w - 1, y + h - 1); // bottom dark shadow
break;
case RIGHT:
g.drawLine(x, y, x + w - 3, y); // top highlight
g.drawLine(x, y + h - 2, x + w - 3, y + h - 2); // bottom shadow
g.drawLine(x + w - 2, y + 2, x + w - 2, y + h - 3); // right shadow
g.drawLine(x + w - 2, y + 1, x + w - 2, y + 1); // top-right dark shadow
g.drawLine(x + w - 2, y + h - 2, x + w - 2, y + h - 2); // bottom-right dark shadow
g.drawLine(x + w - 1, y + 2, x + w - 1, y + h - 3); // right dark shadow
g.drawLine(x, y + h - 1, x + w - 3, y + h - 1); // bottom dark shadow
break;
case BOTTOM:
g.drawLine(x, y, x, y + h - 2); // left highlight
// g.drawLine(x+1, y+h-1, x+1, y+h-1); // bottom-left highlight
g.drawLine(x + 1, y + h - 2, x + w - 1, y + h - 2); // bottom shadow
g.drawLine(x + w - 1, y, x + w - 1, y + h - 2); // right shadow
/*
g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow
g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow
*/
break;
case TOP:
default:
g.drawLine(x, y + 2, x, y + h - 1); // left highlight
g.drawLine(x + 1, y + 1, x + 1, y + 1); // top-left highlight
g.drawLine(x + 2, y, x + w - 3, y); // top highlight
g.drawLine(x + w - 2, y + 2, x + w - 2, y + h - 1); // right shadow
g.drawLine(x + w - 1, y + 2, x + w - 1, y + h - 1); // right dark-shadow
g.drawLine(x + w - 2, y + 1, x + w - 2, y + 1); // top-right shadow
}
}
@Override
protected void paintTabBackground(Graphics gr, int tabPlacement,
int tabIndex,
int x, int y, int w, int h,
boolean isSelected) {
Graphics2D g = (Graphics2D) gr;
g.setColor(!isSelected || selectedColor == null
? tabPane.getBackgroundAt(tabIndex) : selectedColor);
switch (tabPlacement) {
case LEFT:
g.fillRect(x + 1, y + 1, w - 1, h - 3);
break;
case RIGHT:
g.fillRect(x, y + 1, w - 2, h - 3);
break;
case BOTTOM:
if (!isSelected) {
LinearGradientPaint lgp = new LinearGradientPaint(
new Point2D.Float(x, y), new Point2D.Float(x, y + h - 1),
enabledStops, enabledStopColors,
MultipleGradientPaint.CycleMethod.REPEAT);
g.setPaint(lgp);
}
g.fillRect(x + 1, y, w - 3, h - 1);
break;
case TOP:
default:
g.fillRect(x + 1, y + 1, w - 3, h - 1);
}
}
@Override
protected void paintText(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);
if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
Color fg = tabPane.getForegroundAt(tabIndex);
if (isSelected && (fg instanceof UIResource)) {
Color selectedFG = PaletteLookAndFeel.getInstance().getColor(
"TabbedPane.selectedForeground");
if (selectedFG != null) {
fg = selectedFG;
}
}
g.setColor(fg);
PaletteUtilities.drawStringUnderlineCharAt(tabPane, g,
title, mnemIndex,
textRect.x, textRect.y + metrics.getAscent());
} else { // tab disabled
g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
PaletteUtilities.drawStringUnderlineCharAt(tabPane, g,
title, mnemIndex,
textRect.x, textRect.y + metrics.getAscent());
g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
PaletteUtilities.drawStringUnderlineCharAt(tabPane, g,
title, mnemIndex,
textRect.x - 1, textRect.y + metrics.getAscent() - 1);
}
}
}
@Override
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
int width = tabPane.getWidth();
int height = tabPane.getHeight();
Insets insets = tabPane.getInsets();
Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
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);
if (tabsOverlapBorder) {
x -= tabAreaInsets.right;
}
w -= (x - insets.left);
break;
case RIGHT:
w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
if (tabsOverlapBorder) {
w += tabAreaInsets.left;
}
break;
case BOTTOM:
h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
if (tabsOverlapBorder) {
h += tabAreaInsets.top;
}
break;
case TOP:
default:
y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
if (tabsOverlapBorder) {
y -= tabAreaInsets.bottom;
}
h -= (y - insets.top);
}
if (tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque())) {
// Fill region behind content area
Color color = PaletteLookAndFeel.getInstance().getColor("TabbedPane.contentAreaColor");
if (color != null) {
g.setColor(color);
} else if (selectedColor == null || selectedIndex == -1) {
g.setColor(tabPane.getBackground());
} else {
g.setColor(selectedColor);
}
g.fillRect(x, y, w, h);
}
boolean paintBorder = tabPane.getClientProperty("Palette.TabbedPane.paintContentBorder") != Boolean.FALSE;
if (tabPlacement == TOP || paintBorder) {
paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
}
if (tabPlacement == LEFT || paintBorder) {
paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
}
if (tabPlacement == BOTTOM || paintBorder) {
paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
}
if (tabPlacement == RIGHT || paintBorder) {
paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
}
}
@Override
protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
int selectedIndex,
int x, int y, int w, int h) {
Rectangle selRect = selectedIndex < 0 ? null
: getTabBounds(selectedIndex, calcRect);
g.setColor(lightHighlight);
// Draw unbroken line if tabs are not on TOP, OR
// selected tab is not in run adjacent to content, OR
// selected tab is not visible (SCROLL_TAB_LAYOUT)
//
if (tabPlacement != TOP || selectedIndex < 0
|| (selRect.y + selRect.height + 1 < y)
|| (selRect.x < x || selRect.x > x + w)) {
g.drawLine(x, y, x + w - 2, y);
} else {
// Break line to show visual connection to selected tab
g.drawLine(x, y, selRect.x - 1, y);
if (selRect.x + selRect.width < x + w - 2) {
g.drawLine(selRect.x + selRect.width, y,
x + w - 2, y);
} else {
g.setColor(shadow);
g.drawLine(x + w - 2, y, x + w - 2, y);
}
}
}
@Override
protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
int selectedIndex,
int x, int y, int w, int h) {
Rectangle selRect = selectedIndex < 0 ? null
: getTabBounds(selectedIndex, calcRect);
g.setColor(lightHighlight);
// Draw unbroken line if tabs are not on LEFT, OR
// selected tab is not in run adjacent to content, OR
// selected tab is not visible (SCROLL_TAB_LAYOUT)
//
if (tabPlacement != LEFT || selectedIndex < 0
|| (selRect.x + selRect.width + 1 < x)
|| (selRect.y < y || selRect.y > y + h)) {
g.drawLine(x, y, x, y + h - 2);
} else {
// Break line to show visual connection to selected tab
g.drawLine(x, y, x, selRect.y - 1);
if (selRect.y + selRect.height < y + h - 2) {
g.drawLine(x, selRect.y + selRect.height,
x, y + h - 2);
}
}
}
@Override
protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
int selectedIndex,
int x, int y, int w, int h) {
Rectangle selRect = selectedIndex < 0 ? null
: getTabBounds(selectedIndex, calcRect);
g.setColor(shadow);
// Draw unbroken line if tabs are not on BOTTOM, OR
// selected tab is not in run adjacent to content, OR
// selected tab is not visible (SCROLL_TAB_LAYOUT)
//
if (tabPlacement != BOTTOM || selectedIndex < 0
|| (selRect.y - 1 > h)
|| (selRect.x < x || selRect.x > x + w)) {
//g.drawLine(x + 1, y + h - 2, x + w - 1, y + h - 2);
g.setColor(shadow);
g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
} else {
// Break line to show visual connection to selected tab
//g.drawLine(x + 1, y + h - 2, selRect.x - 1, y + h - 2);
g.setColor(shadow);
g.drawLine(x, y + h - 1, selRect.x , y + h - 1);
if (selRect.x + selRect.width < x + w - 2) {
//g.setColor(shadow);
//g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2);
g.setColor(shadow);
g.drawLine(selRect.x + selRect.width-1, y + h - 1, x + w - 1, y + h - 1);
}
}
}
@Override
protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
int selectedIndex,
int x, int y, int w, int h) {
Rectangle selRect = selectedIndex < 0 ? null
: getTabBounds(selectedIndex, calcRect);
g.setColor(shadow);
// Draw unbroken line if tabs are not on RIGHT, OR
// selected tab is not in run adjacent to content, OR
// selected tab is not visible (SCROLL_TAB_LAYOUT)
//
if (tabPlacement != RIGHT || selectedIndex < 0
|| (selRect.x - 1 > w)
|| (selRect.y < y || selRect.y > y + h)) {
g.drawLine(x + w - 2, y + 1, x + w - 2, y + h - 3);
g.setColor(darkShadow);
g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
} else {
// Break line to show visual connection to selected tab
g.drawLine(x + w - 2, y + 1, x + w - 2, selRect.y - 1);
g.setColor(darkShadow);
g.drawLine(x + w - 1, y, x + w - 1, selRect.y - 1);
if (selRect.y + selRect.height < y + h - 2) {
g.setColor(shadow);
g.drawLine(x + w - 2, selRect.y + selRect.height,
x + w - 2, y + h - 2);
g.setColor(darkShadow);
g.drawLine(x + w - 1, selRect.y + selRect.height,
x + w - 1, y + h - 2);
}
}
}
@Override
protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
Rectangle tabRect = rects[tabIndex];
int nudge = 0;
switch (tabPlacement) {
case LEFT:
nudge = isSelected ? -1 : 1;
break;
case RIGHT:
nudge = isSelected ? 1 : -1;
break;
case BOTTOM:
case TOP:
default:
nudge = tabRect.width % 2;
}
return nudge;
}
@Override
protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
Rectangle tabRect = rects[tabIndex];
int nudge = 0;
switch (tabPlacement) {
case BOTTOM:
//nudge = isSelected? 1 : -1;
nudge = -1;
break;
case LEFT:
case RIGHT:
nudge = tabRect.height % 2;
break;
case TOP:
default:
nudge = isSelected ? -1 : 1;
}
return nudge;
}
private boolean scrollableTabLayoutEnabled() {
return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
}
@Override
protected LayoutManager createLayoutManager() {
if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
return super.createLayoutManager();
}
return new TabbedPaneLayout();
}
@Override
protected FontMetrics getFontMetrics() {
//Font font = tabPane.getFont();
Font font = PaletteLookAndFeel.getInstance().getFont("TabbedPane.selectedFont");
return tabPane.getFontMetrics(font);
}
protected class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout {
public TabbedPaneLayout() {
PaletteTabbedPaneUI.this.super();
}
@Override
protected void normalizeTabRuns(int tabPlacement, int tabCount,
int start, int max) {
// Only normalize the runs for top & bottom; normalizing
// doesn't look right for Metal's vertical tabs
// because the last run isn't padded and it looks odd to have
// fat tabs in the first vertical runs, but slimmer ones in the
// last (this effect isn't noticeable for horizontal tabs).
if (tabPlacement == TOP || tabPlacement == BOTTOM) {
super.normalizeTabRuns(tabPlacement, tabCount, start, max);
}
}
// Don't rotate runs!
@Override
protected void rotateTabRuns(int tabPlacement, int selectedRun) {
}
// Don't pad selected tab
@Override
protected void padSelectedTab(int tabPlacement, int selectedIndex) {
}
}
}