1. /*
  2. * @(#)SynthTabbedPaneUI.java 1.32 04/04/02
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.synth;
  8. import javax.swing.*;
  9. import javax.swing.event.*;
  10. import javax.swing.plaf.*;
  11. import javax.swing.plaf.basic.*;
  12. import javax.swing.text.View;
  13. import java.awt.*;
  14. import java.awt.event.*;
  15. import java.beans.PropertyChangeListener;
  16. import java.beans.PropertyChangeEvent;
  17. import java.util.Vector;
  18. import java.util.Hashtable;
  19. import sun.swing.plaf.synth.SynthUI;
  20. import com.sun.java.swing.SwingUtilities2;
  21. /**
  22. * A Synth L&F implementation of TabbedPaneUI.
  23. *
  24. * @version 1.32, 04/02/04
  25. * @author Scott Violet
  26. */
  27. /**
  28. * Looks up 'selectedTabPadInsets' from the Style, which will be additional
  29. * insets for the selected tab.
  30. */
  31. class SynthTabbedPaneUI extends BasicTabbedPaneUI implements SynthUI, PropertyChangeListener {
  32. private SynthContext tabAreaContext;
  33. private SynthContext tabContext;
  34. private SynthContext tabContentContext;
  35. private SynthStyle style;
  36. private SynthStyle tabStyle;
  37. private SynthStyle tabAreaStyle;
  38. private SynthStyle tabContentStyle;
  39. private Rectangle tabAreaBounds = new Rectangle();
  40. public static ComponentUI createUI(JComponent c) {
  41. return new SynthTabbedPaneUI();
  42. }
  43. private boolean scrollableTabLayoutEnabled() {
  44. return (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT);
  45. }
  46. protected void installDefaults() {
  47. updateStyle(tabPane);
  48. }
  49. private void updateStyle(JTabbedPane c) {
  50. SynthContext context = getContext(c, ENABLED);
  51. SynthStyle oldStyle = style;
  52. style = SynthLookAndFeel.updateStyle(context, this);
  53. // Add properties other than JComponent colors, Borders and
  54. // opacity settings here:
  55. if (style != oldStyle) {
  56. tabRunOverlay =
  57. style.getInt(context, "TabbedPane.tabRunOverlay", 0);
  58. textIconGap = style.getInt(context, "TabbedPane.textIconGap", 0);
  59. selectedTabPadInsets = (Insets)style.get(context,
  60. "TabbedPane.selectedTabPadInsets");
  61. if (selectedTabPadInsets == null) {
  62. selectedTabPadInsets = new Insets(0, 0, 0, 0);
  63. }
  64. if (oldStyle != null) {
  65. uninstallKeyboardActions();
  66. installKeyboardActions();
  67. }
  68. }
  69. context.dispose();
  70. if (tabContext != null) {
  71. tabContext.dispose();
  72. }
  73. tabContext = getContext(c, Region.TABBED_PANE_TAB, ENABLED);
  74. this.tabStyle = SynthLookAndFeel.updateStyle(tabContext, this);
  75. tabInsets = tabStyle.getInsets(tabContext, null);
  76. if (tabAreaContext != null) {
  77. tabAreaContext.dispose();
  78. }
  79. tabAreaContext = getContext(c, Region.TABBED_PANE_TAB_AREA, ENABLED);
  80. this.tabAreaStyle = SynthLookAndFeel.updateStyle(tabAreaContext, this);
  81. tabAreaInsets = tabAreaStyle.getInsets(tabAreaContext, null);
  82. if (tabContentContext != null) {
  83. tabContentContext.dispose();
  84. }
  85. tabContentContext = getContext(c, Region.TABBED_PANE_CONTENT, ENABLED);
  86. this.tabContentStyle = SynthLookAndFeel.updateStyle(tabContentContext,
  87. this);
  88. contentBorderInsets =
  89. tabContentStyle.getInsets(tabContentContext, null);
  90. }
  91. protected void installListeners() {
  92. super.installListeners();
  93. tabPane.addPropertyChangeListener(this);
  94. }
  95. protected void uninstallListeners() {
  96. super.uninstallListeners();
  97. tabPane.removePropertyChangeListener(this);
  98. }
  99. protected void uninstallDefaults() {
  100. SynthContext context = getContext(tabPane, ENABLED);
  101. style.uninstallDefaults(context);
  102. context.dispose();
  103. style = null;
  104. tabStyle.uninstallDefaults(tabContext);
  105. tabContext.dispose();
  106. tabContext = null;
  107. tabStyle = null;
  108. tabAreaStyle.uninstallDefaults(tabAreaContext);
  109. tabAreaContext.dispose();
  110. tabAreaContext = null;
  111. tabAreaStyle = null;
  112. tabContentStyle.uninstallDefaults(tabContentContext);
  113. tabContentContext.dispose();
  114. tabContentContext = null;
  115. tabContentStyle = null;
  116. }
  117. public SynthContext getContext(JComponent c) {
  118. return getContext(c, getComponentState(c));
  119. }
  120. public SynthContext getContext(JComponent c, int state) {
  121. return SynthContext.getContext(SynthContext.class, c,
  122. SynthLookAndFeel.getRegion(c),style, state);
  123. }
  124. public SynthContext getContext(JComponent c, Region subregion) {
  125. return getContext(c, subregion, getComponentState(c));
  126. }
  127. private SynthContext getContext(JComponent c, Region subregion, int state){
  128. SynthStyle style = null;
  129. Class klass = SynthContext.class;
  130. if (subregion == Region.TABBED_PANE_TAB) {
  131. style = tabStyle;
  132. }
  133. else if (subregion == Region.TABBED_PANE_TAB_AREA) {
  134. style = tabAreaStyle;
  135. }
  136. else if (subregion == Region.TABBED_PANE_CONTENT) {
  137. style = tabContentStyle;
  138. }
  139. return SynthContext.getContext(klass, c, subregion, style, state);
  140. }
  141. private Region getRegion(JComponent c) {
  142. return SynthLookAndFeel.getRegion(c);
  143. }
  144. private int getComponentState(JComponent c) {
  145. return SynthLookAndFeel.getComponentState(c);
  146. }
  147. protected JButton createScrollButton(int direction) {
  148. return new SynthScrollableTabButton(direction);
  149. }
  150. public void propertyChange(PropertyChangeEvent e) {
  151. if (SynthLookAndFeel.shouldUpdateStyle(e)) {
  152. updateStyle(tabPane);
  153. }
  154. }
  155. public void update(Graphics g, JComponent c) {
  156. SynthContext context = getContext(c);
  157. SynthLookAndFeel.update(context, g);
  158. context.getPainter().paintTabbedPaneBackground(context,
  159. g, 0, 0, c.getWidth(), c.getHeight());
  160. paint(context, g);
  161. context.dispose();
  162. }
  163. public void paintBorder(SynthContext context, Graphics g, int x,
  164. int y, int w, int h) {
  165. context.getPainter().paintTabbedPaneBorder(context, g, x, y, w, h);
  166. }
  167. public void paint(Graphics g, JComponent c) {
  168. SynthContext context = getContext(c);
  169. paint(context, g);
  170. context.dispose();
  171. }
  172. protected void paint(SynthContext context, Graphics g) {
  173. int selectedIndex = tabPane.getSelectedIndex();
  174. int tabPlacement = tabPane.getTabPlacement();
  175. ensureCurrentLayout();
  176. // Paint tab area
  177. // If scrollable tabs are enabled, the tab area will be
  178. // painted by the scrollable tab panel instead.
  179. //
  180. if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
  181. Insets insets = tabPane.getInsets();
  182. int x = insets.left;
  183. int y = insets.top;
  184. int width = tabPane.getWidth() - insets.left - insets.right;
  185. int height = tabPane.getHeight() - insets.top - insets.bottom;
  186. int size;
  187. switch(tabPlacement) {
  188. case LEFT:
  189. width = calculateTabAreaWidth(tabPlacement, runCount,
  190. maxTabWidth);
  191. break;
  192. case RIGHT:
  193. size = calculateTabAreaWidth(tabPlacement, runCount,
  194. maxTabWidth);
  195. x = x + width - size;
  196. width = size;
  197. break;
  198. case BOTTOM:
  199. size = calculateTabAreaHeight(tabPlacement, runCount,
  200. maxTabHeight);
  201. y = y + height - size;
  202. height = size;
  203. break;
  204. case TOP:
  205. default:
  206. height = calculateTabAreaHeight(tabPlacement, runCount,
  207. maxTabHeight);
  208. }
  209. tabAreaBounds.setBounds(x, y, width, height);
  210. if (g.getClipBounds().intersects(tabAreaBounds)) {
  211. paintTabArea(tabAreaContext, g, tabPlacement,
  212. selectedIndex, tabAreaBounds);
  213. }
  214. }
  215. // Paint content border
  216. paintContentBorder(tabContentContext, g, tabPlacement, selectedIndex);
  217. }
  218. protected void paintTabArea(Graphics g, int tabPlacement,
  219. int selectedIndex) {
  220. // This can be invoked from ScrollabeTabPanel
  221. Insets insets = tabPane.getInsets();
  222. int x = insets.left;
  223. int y = insets.top;
  224. int width = tabPane.getWidth() - insets.left - insets.right;
  225. int height = tabPane.getHeight() - insets.top - insets.bottom;
  226. paintTabArea(tabAreaContext, g, tabPlacement, selectedIndex,
  227. new Rectangle(x, y, width, height));
  228. }
  229. protected void paintTabArea(SynthContext ss, Graphics g,
  230. int tabPlacement, int selectedIndex,
  231. Rectangle tabAreaBounds) {
  232. Rectangle clipRect = g.getClipBounds();
  233. // Paint the tab area.
  234. SynthLookAndFeel.updateSubregion(ss, g, tabAreaBounds);
  235. ss.getPainter().paintTabbedPaneTabAreaBackground(ss, g,
  236. tabAreaBounds.x, tabAreaBounds.y, tabAreaBounds.width,
  237. tabAreaBounds.height);
  238. ss.getPainter().paintTabbedPaneTabAreaBorder(ss, g, tabAreaBounds.x,
  239. tabAreaBounds.y, tabAreaBounds.width, tabAreaBounds.height);
  240. int tabCount = tabPane.getTabCount();
  241. Rectangle iconRect = new Rectangle(),
  242. textRect = new Rectangle();
  243. // Paint tabRuns of tabs from back to front
  244. for (int i = runCount - 1; i >= 0; i--) {
  245. int start = tabRuns[i];
  246. int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
  247. int end = (next != 0? next - 1: tabCount - 1);
  248. for (int j = start; j <= end; j++) {
  249. if (rects[j].intersects(clipRect) && selectedIndex != j) {
  250. paintTab(tabContext, g, tabPlacement, rects, j, iconRect,
  251. textRect);
  252. }
  253. }
  254. }
  255. if (selectedIndex >= 0) {
  256. if (rects[selectedIndex].intersects(clipRect)) {
  257. paintTab(tabContext, g, tabPlacement, rects, selectedIndex,
  258. iconRect, textRect);
  259. }
  260. }
  261. }
  262. protected void paintTab(SynthContext ss, Graphics g,
  263. int tabPlacement, Rectangle[] rects, int tabIndex,
  264. Rectangle iconRect, Rectangle textRect) {
  265. Rectangle tabRect = rects[tabIndex];
  266. int selectedIndex = tabPane.getSelectedIndex();
  267. boolean isSelected = selectedIndex == tabIndex;
  268. updateTabContext(tabIndex, isSelected, false,
  269. (getFocusIndex() == tabIndex));
  270. SynthLookAndFeel.updateSubregion(ss, g, tabRect);
  271. tabContext.getPainter().paintTabbedPaneTabBackground(tabContext,
  272. g, tabRect.x, tabRect.y, tabRect.width,
  273. tabRect.height, tabIndex);
  274. tabContext.getPainter().paintTabbedPaneTabBorder(tabContext, g,
  275. tabRect.x, tabRect.y, tabRect.width, tabRect.height,
  276. tabIndex);
  277. String title = tabPane.getTitleAt(tabIndex);
  278. Font font = ss.getStyle().getFont(ss);
  279. FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font);
  280. Icon icon = getIconForTab(tabIndex);
  281. layoutLabel(ss, tabPlacement, metrics, tabIndex, title, icon,
  282. tabRect, iconRect, textRect, isSelected);
  283. paintText(ss, g, tabPlacement, font, metrics,
  284. tabIndex, title, textRect, isSelected);
  285. paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
  286. }
  287. protected void layoutLabel(SynthContext ss, int tabPlacement,
  288. FontMetrics metrics, int tabIndex,
  289. String title, Icon icon,
  290. Rectangle tabRect, Rectangle iconRect,
  291. Rectangle textRect, boolean isSelected ) {
  292. View v = getTextViewForTab(tabIndex);
  293. if (v != null) {
  294. tabPane.putClientProperty("html", v);
  295. }
  296. textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
  297. ss.getStyle().getGraphicsUtils(ss).layoutText(ss, metrics, title,
  298. icon, SwingUtilities.CENTER, SwingUtilities.CENTER,
  299. SwingUtilities.LEADING, SwingUtilities.TRAILING,
  300. tabRect, iconRect, textRect, textIconGap);
  301. tabPane.putClientProperty("html", null);
  302. int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
  303. int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
  304. iconRect.x += xNudge;
  305. iconRect.y += yNudge;
  306. textRect.x += xNudge;
  307. textRect.y += yNudge;
  308. }
  309. protected void paintText(SynthContext ss,
  310. Graphics g, int tabPlacement,
  311. Font font, FontMetrics metrics, int tabIndex,
  312. String title, Rectangle textRect,
  313. boolean isSelected) {
  314. g.setFont(font);
  315. View v = getTextViewForTab(tabIndex);
  316. if (v != null) {
  317. // html
  318. v.paint(g, textRect);
  319. } else {
  320. // plain text
  321. int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
  322. g.setColor(ss.getStyle().getColor(ss, ColorType.TEXT_FOREGROUND));
  323. ss.getStyle().getGraphicsUtils(ss).paintText(ss, g, title,
  324. textRect, mnemIndex);
  325. }
  326. }
  327. protected void paintContentBorder(SynthContext ss, Graphics g,
  328. int tabPlacement, int selectedIndex) {
  329. int width = tabPane.getWidth();
  330. int height = tabPane.getHeight();
  331. Insets insets = tabPane.getInsets();
  332. int x = insets.left;
  333. int y = insets.top;
  334. int w = width - insets.right - insets.left;
  335. int h = height - insets.top - insets.bottom;
  336. switch(tabPlacement) {
  337. case LEFT:
  338. x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  339. w -= (x - insets.left);
  340. break;
  341. case RIGHT:
  342. w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  343. break;
  344. case BOTTOM:
  345. h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  346. break;
  347. case TOP:
  348. default:
  349. y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  350. h -= (y - insets.top);
  351. }
  352. SynthLookAndFeel.updateSubregion(ss, g, new Rectangle(x, y, w, h));
  353. ss.getPainter().paintTabbedPaneContentBackground(ss, g, x, y,
  354. w, h);
  355. ss.getPainter().paintTabbedPaneContentBorder(ss, g, x, y, w, h);
  356. }
  357. private void ensureCurrentLayout() {
  358. if (!tabPane.isValid()) {
  359. tabPane.validate();
  360. }
  361. /* If tabPane doesn't have a peer yet, the validate() call will
  362. * silently fail. We handle that by forcing a layout if tabPane
  363. * is still invalid. See bug 4237677.
  364. */
  365. if (!tabPane.isValid()) {
  366. TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
  367. layout.calculateLayoutInfo();
  368. }
  369. }
  370. protected int calculateMaxTabHeight(int tabPlacement) {
  371. FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont(
  372. tabContext));
  373. int tabCount = tabPane.getTabCount();
  374. int result = 0;
  375. int fontHeight = metrics.getHeight();
  376. for(int i = 0; i < tabCount; i++) {
  377. result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
  378. }
  379. return result;
  380. }
  381. protected int calculateTabWidth(int tabPlacement, int tabIndex,
  382. FontMetrics metrics) {
  383. Icon icon = getIconForTab(tabIndex);
  384. Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
  385. int width = tabInsets.left + tabInsets.right + 3;
  386. if (icon != null) {
  387. width += icon.getIconWidth() + textIconGap;
  388. }
  389. View v = getTextViewForTab(tabIndex);
  390. if (v != null) {
  391. // html
  392. width += (int)v.getPreferredSpan(View.X_AXIS);
  393. } else {
  394. // plain text
  395. String title = tabPane.getTitleAt(tabIndex);
  396. width += tabContext.getStyle().getGraphicsUtils(tabContext).
  397. computeStringWidth(tabContext, metrics.getFont(),
  398. metrics, title);
  399. }
  400. return width;
  401. }
  402. protected int calculateMaxTabWidth(int tabPlacement) {
  403. FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont(
  404. tabContext));
  405. int tabCount = tabPane.getTabCount();
  406. int result = 0;
  407. for(int i = 0; i < tabCount; i++) {
  408. result = Math.max(calculateTabWidth(tabPlacement, i, metrics),
  409. result);
  410. }
  411. return result;
  412. }
  413. protected Insets getTabInsets(int tabPlacement, int tabIndex) {
  414. updateTabContext(tabIndex, false, false,
  415. (getFocusIndex() == tabIndex));
  416. return tabInsets;
  417. }
  418. protected FontMetrics getFontMetrics() {
  419. return getFontMetrics(tabContext.getStyle().getFont(tabContext));
  420. }
  421. protected FontMetrics getFontMetrics(Font font) {
  422. return tabPane.getFontMetrics(font);
  423. }
  424. private void updateTabContext(int index, boolean selected,
  425. boolean isMouseOver, boolean hasFocus) {
  426. int state = 0;
  427. if (!tabPane.isEnabledAt(index)) {
  428. state |= SynthConstants.DISABLED;
  429. }
  430. else if (selected) {
  431. state |= (SynthConstants.ENABLED | SynthConstants.SELECTED);
  432. }
  433. else if (isMouseOver) {
  434. state |= (SynthConstants.ENABLED | SynthConstants.MOUSE_OVER);
  435. }
  436. else {
  437. state = SynthLookAndFeel.getComponentState(tabPane);
  438. state &= ~SynthConstants.FOCUSED; // don't use tabbedpane focus state
  439. }
  440. if (hasFocus && tabPane.hasFocus()) {
  441. state |= SynthConstants.FOCUSED; // individual tab has focus
  442. }
  443. tabContext.setComponentState(state);
  444. }
  445. private class SynthScrollableTabButton extends SynthArrowButton implements
  446. UIResource {
  447. public SynthScrollableTabButton(int direction) {
  448. super(direction);
  449. }
  450. }
  451. }