1. /*
  2. * @(#)BasicTabbedPaneUI.java 1.93 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.plaf.basic;
  11. import javax.swing.*;
  12. import javax.swing.event.*;
  13. import javax.swing.plaf.*;
  14. import javax.swing.text.View;
  15. import java.awt.*;
  16. import java.awt.event.*;
  17. import java.beans.PropertyChangeListener;
  18. import java.beans.PropertyChangeEvent;
  19. import java.util.Vector;
  20. /**
  21. * A Basic L&F implementation of TabbedPaneUI.
  22. *
  23. * @version 1.87 06/08/99
  24. * @author Amy Fowler
  25. * @author Philip Milne
  26. * @author Steve Wilson
  27. * @author Tom Santos
  28. * @author Dave Moore
  29. */
  30. public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants {
  31. // Instance variables initialized at installation
  32. protected JTabbedPane tabPane;
  33. protected Color highlight;
  34. protected Color lightHighlight;
  35. protected Color shadow;
  36. protected Color darkShadow;
  37. protected Color focus;
  38. protected int textIconGap;
  39. protected int tabRunOverlay;
  40. protected Insets tabInsets;
  41. protected Insets selectedTabPadInsets;
  42. protected Insets tabAreaInsets;
  43. protected Insets contentBorderInsets;
  44. /**
  45. * As of Java 2 platform v1.3 this previously undocumented field is no
  46. * longer used.
  47. * Key bindings are now defined by the LookAndFeel, please refer to
  48. * the key bindings specification for further details.
  49. *
  50. * @deprecated As of Java 2 platform v1.3.
  51. */
  52. protected KeyStroke upKey;
  53. /**
  54. * As of Java 2 platform v1.3 this previously undocumented field is no
  55. * longer used.
  56. * Key bindings are now defined by the LookAndFeel, please refer to
  57. * the key bindings specification for further details.
  58. *
  59. * @deprecated As of Java 2 platform v1.3.
  60. */
  61. protected KeyStroke downKey;
  62. /**
  63. * As of Java 2 platform v1.3 this previously undocumented field is no
  64. * longer used.
  65. * Key bindings are now defined by the LookAndFeel, please refer to
  66. * the key bindings specification for further details.
  67. *
  68. * @deprecated As of Java 2 platform v1.3.
  69. */
  70. protected KeyStroke leftKey;
  71. /**
  72. * As of Java 2 platform v1.3 this previously undocumented field is no
  73. * longer used.
  74. * Key bindings are now defined by the LookAndFeel, please refer to
  75. * the key bindings specification for further details.
  76. *
  77. * @deprecated As of Java 2 platform v1.3.
  78. */
  79. protected KeyStroke rightKey;
  80. // Transient variables (recalculated each time TabbedPane is layed out)
  81. protected int tabRuns[] = new int[10];
  82. protected int runCount = 0;
  83. protected int selectedRun = -1;
  84. protected Rectangle rects[] = new Rectangle[0];
  85. protected int maxTabHeight;
  86. protected int maxTabWidth;
  87. // Listeners
  88. protected ChangeListener tabChangeListener;
  89. protected PropertyChangeListener propertyChangeListener;
  90. protected MouseListener mouseListener;
  91. protected FocusListener focusListener;
  92. // PENDING(api): See comment for ContainerHandler
  93. private ContainerListener containerListener;
  94. // Private instance data
  95. private Insets currentPadInsets = new Insets(0,0,0,0);
  96. private Insets currentTabAreaInsets = new Insets(0,0,0,0);
  97. private Component visibleComponent;
  98. // PENDING(api): See comment for ContainerHandler
  99. private Vector htmlViews;
  100. // UI creation
  101. public static ComponentUI createUI(JComponent c) {
  102. return new BasicTabbedPaneUI();
  103. }
  104. // UI Installation/De-installation
  105. public void installUI(JComponent c) {
  106. this.tabPane = (JTabbedPane)c;
  107. c.setLayout(createLayoutManager());
  108. installDefaults();
  109. installListeners();
  110. installKeyboardActions();
  111. }
  112. public void uninstallUI(JComponent c) {
  113. uninstallKeyboardActions();
  114. uninstallListeners();
  115. uninstallDefaults();
  116. c.setLayout(null);
  117. this.tabPane = null;
  118. }
  119. protected LayoutManager createLayoutManager() {
  120. return new TabbedPaneLayout();
  121. }
  122. protected void installDefaults() {
  123. LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
  124. "TabbedPane.foreground", "TabbedPane.font");
  125. highlight = UIManager.getColor("TabbedPane.highlight");
  126. lightHighlight = UIManager.getColor("TabbedPane.lightHighlight");
  127. shadow = UIManager.getColor("TabbedPane.shadow");
  128. darkShadow = UIManager.getColor("TabbedPane.darkShadow");
  129. focus = UIManager.getColor("TabbedPane.focus");
  130. textIconGap = UIManager.getInt("TabbedPane.textIconGap");
  131. tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
  132. selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
  133. tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
  134. contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
  135. tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
  136. }
  137. protected void uninstallDefaults() {
  138. highlight = null;
  139. lightHighlight = null;
  140. shadow = null;
  141. darkShadow = null;
  142. focus = null;
  143. tabInsets = null;
  144. selectedTabPadInsets = null;
  145. tabAreaInsets = null;
  146. contentBorderInsets = null;
  147. }
  148. protected void installListeners() {
  149. if ((propertyChangeListener = createPropertyChangeListener()) != null) {
  150. tabPane.addPropertyChangeListener(propertyChangeListener);
  151. }
  152. if ((tabChangeListener = createChangeListener()) != null) {
  153. tabPane.addChangeListener(tabChangeListener);
  154. }
  155. if ((mouseListener = createMouseListener()) != null) {
  156. tabPane.addMouseListener(mouseListener);
  157. }
  158. if ((focusListener = createFocusListener()) != null) {
  159. tabPane.addFocusListener(focusListener);
  160. }
  161. // PENDING(api) : See comment for ContainerHandler
  162. if ((containerListener = new ContainerHandler()) != null) {
  163. tabPane.addContainerListener(containerListener);
  164. if (tabPane.getTabCount()>0) {
  165. htmlViews = createHTMLVector();
  166. }
  167. }
  168. }
  169. protected void uninstallListeners() {
  170. if (mouseListener != null) {
  171. tabPane.removeMouseListener(mouseListener);
  172. mouseListener = null;
  173. }
  174. if (focusListener != null) {
  175. tabPane.removeFocusListener(focusListener);
  176. focusListener = null;
  177. }
  178. // PENDING(api): See comment for ContainerHandler
  179. if (containerListener != null) {
  180. tabPane.removeContainerListener(containerListener);
  181. containerListener = null;
  182. if (htmlViews!=null) {
  183. htmlViews.removeAllElements();
  184. htmlViews = null;
  185. }
  186. }
  187. if (tabChangeListener != null) {
  188. tabPane.removeChangeListener(tabChangeListener);
  189. tabChangeListener = null;
  190. }
  191. if (propertyChangeListener != null) {
  192. tabPane.removePropertyChangeListener(propertyChangeListener);
  193. propertyChangeListener = null;
  194. }
  195. }
  196. protected MouseListener createMouseListener() {
  197. return new MouseHandler();
  198. }
  199. protected FocusListener createFocusListener() {
  200. return new FocusHandler();
  201. }
  202. protected ChangeListener createChangeListener() {
  203. return new TabSelectionHandler();
  204. }
  205. protected PropertyChangeListener createPropertyChangeListener() {
  206. return new PropertyChangeHandler();
  207. }
  208. protected void installKeyboardActions() {
  209. InputMap km = getInputMap(JComponent.
  210. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  211. SwingUtilities.replaceUIInputMap(tabPane, JComponent.
  212. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  213. km);
  214. km = getInputMap(JComponent.WHEN_FOCUSED);
  215. SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
  216. ActionMap am = getActionMap();
  217. SwingUtilities.replaceUIActionMap(tabPane, am);
  218. }
  219. InputMap getInputMap(int condition) {
  220. if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
  221. return (InputMap)UIManager.get("TabbedPane.ancestorInputMap");
  222. }
  223. else if (condition == JComponent.WHEN_FOCUSED) {
  224. return (InputMap)UIManager.get("TabbedPane.focusInputMap");
  225. }
  226. return null;
  227. }
  228. ActionMap getActionMap() {
  229. ActionMap map = (ActionMap)UIManager.get("TabbedPane.actionMap");
  230. if (map == null) {
  231. map = createActionMap();
  232. if (map != null) {
  233. UIManager.put("TabbedPane.actionMap", map);
  234. }
  235. }
  236. return map;
  237. }
  238. ActionMap createActionMap() {
  239. ActionMap map = new ActionMapUIResource();
  240. map.put("navigateRight", new RightAction());
  241. map.put("navigateLeft", new LeftAction());
  242. map.put("navigateUp", new UpAction());
  243. map.put("navigateDown", new DownAction());
  244. map.put("navigatePageUp", new PageUpAction());
  245. map.put("navigatePageDown", new PageDownAction());
  246. map.put("requestFocus", new RequestFocusAction());
  247. map.put("requestFocusForVisibleComponent",
  248. new RequestFocusForVisibleAction());
  249. return map;
  250. }
  251. protected void uninstallKeyboardActions() {
  252. SwingUtilities.replaceUIActionMap(tabPane, null);
  253. SwingUtilities.replaceUIInputMap(tabPane, JComponent.
  254. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  255. null);
  256. SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED,
  257. null);
  258. }
  259. // Geometry
  260. public Dimension getPreferredSize(JComponent c) {
  261. // Default to LayoutManager's preferredLayoutSize
  262. return null;
  263. }
  264. public Dimension getMinimumSize(JComponent c) {
  265. // Default to LayoutManager's minimumLayoutSize
  266. return null;
  267. }
  268. public Dimension getMaximumSize(JComponent c) {
  269. // Default to LayoutManager's maximumLayoutSize
  270. return null;
  271. }
  272. // UI Rendering
  273. public void paint(Graphics g, JComponent c) {
  274. int selectedIndex = tabPane.getSelectedIndex();
  275. int tabPlacement = tabPane.getTabPlacement();
  276. int tabCount = tabPane.getTabCount();
  277. ensureCurrentLayout();
  278. Rectangle iconRect = new Rectangle(),
  279. textRect = new Rectangle();
  280. Rectangle clipRect = g.getClipBounds();
  281. Insets insets = tabPane.getInsets();
  282. // Paint tabRuns of tabs from back to front
  283. for (int i = runCount - 1; i >= 0; i--) {
  284. int start = tabRuns[i];
  285. int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
  286. int end = (next != 0? next - 1: tabCount - 1);
  287. for (int j = start; j <= end; j++) {
  288. if (rects[j].intersects(clipRect)) {
  289. paintTab(g, tabPlacement, rects, j, iconRect, textRect);
  290. }
  291. }
  292. }
  293. // Paint selected tab if its in the front run
  294. // since it may overlap other tabs
  295. if (selectedIndex >= 0 && getRunForTab(tabCount, selectedIndex) == 0) {
  296. if (rects[selectedIndex].intersects(clipRect)) {
  297. paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
  298. }
  299. }
  300. // Paint content border
  301. paintContentBorder(g, tabPlacement, selectedIndex);
  302. }
  303. protected void paintTab(Graphics g, int tabPlacement,
  304. Rectangle[] rects, int tabIndex,
  305. Rectangle iconRect, Rectangle textRect) {
  306. Rectangle tabRect = rects[tabIndex];
  307. int selectedIndex = tabPane.getSelectedIndex();
  308. boolean isSelected = selectedIndex == tabIndex;
  309. paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
  310. tabRect.width, tabRect.height, isSelected);
  311. paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
  312. tabRect.width, tabRect.height, isSelected);
  313. String title = tabPane.getTitleAt(tabIndex);
  314. Font font = tabPane.getFont();
  315. FontMetrics metrics = g.getFontMetrics(font);
  316. Icon icon = getIconForTab(tabIndex);
  317. layoutLabel(tabPlacement, metrics, tabIndex, title, icon,
  318. tabRect, iconRect, textRect, isSelected);
  319. paintText(g, tabPlacement, font, metrics,
  320. tabIndex, title, textRect, isSelected);
  321. paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
  322. paintFocusIndicator(g, tabPlacement, rects, tabIndex,
  323. iconRect, textRect, isSelected);
  324. }
  325. protected void layoutLabel(int tabPlacement,
  326. FontMetrics metrics, int tabIndex,
  327. String title, Icon icon,
  328. Rectangle tabRect, Rectangle iconRect,
  329. Rectangle textRect, boolean isSelected ) {
  330. textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
  331. SwingUtilities.layoutCompoundLabel((JComponent) tabPane,
  332. metrics, title, icon,
  333. SwingUtilities.CENTER,
  334. SwingUtilities.CENTER,
  335. SwingUtilities.CENTER,
  336. SwingUtilities.TRAILING,
  337. tabRect,
  338. iconRect,
  339. textRect,
  340. textIconGap);
  341. int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
  342. int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
  343. iconRect.x += xNudge;
  344. iconRect.y += yNudge;
  345. textRect.x += xNudge;
  346. textRect.y += yNudge;
  347. }
  348. protected void paintIcon(Graphics g, int tabPlacement,
  349. int tabIndex, Icon icon, Rectangle iconRect,
  350. boolean isSelected ) {
  351. if (icon != null) {
  352. icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
  353. }
  354. }
  355. protected void paintText(Graphics g, int tabPlacement,
  356. Font font, FontMetrics metrics, int tabIndex,
  357. String title, Rectangle textRect,
  358. boolean isSelected) {
  359. g.setFont(font);
  360. View v;
  361. if (htmlViews!=null &&
  362. (v = (View)htmlViews.elementAt(tabIndex))!=null) {
  363. v.paint(g, textRect);
  364. } else {
  365. if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
  366. g.setColor(tabPane.getForegroundAt(tabIndex));
  367. g.drawString(title,
  368. textRect.x,
  369. textRect.y + metrics.getAscent());
  370. } else { // tab disabled
  371. g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
  372. g.drawString(title,
  373. textRect.x, textRect.y + metrics.getAscent());
  374. g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
  375. g.drawString(title,
  376. textRect.x - 1, textRect.y + metrics.getAscent() - 1);
  377. }
  378. }
  379. }
  380. protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
  381. Rectangle tabRect = rects[tabIndex];
  382. int nudge = 0;
  383. switch(tabPlacement) {
  384. case LEFT:
  385. nudge = isSelected? -1 : 1;
  386. break;
  387. case RIGHT:
  388. nudge = isSelected? 1 : -1;
  389. break;
  390. case BOTTOM:
  391. case TOP:
  392. default:
  393. nudge = tabRect.width % 2;
  394. }
  395. return nudge;
  396. }
  397. protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
  398. Rectangle tabRect = rects[tabIndex];
  399. int nudge = 0;
  400. switch(tabPlacement) {
  401. case BOTTOM:
  402. nudge = isSelected? 1 : -1;
  403. break;
  404. case LEFT:
  405. case RIGHT:
  406. nudge = tabRect.height % 2;
  407. break;
  408. case TOP:
  409. default:
  410. nudge = isSelected? -1 : 1;;
  411. }
  412. return nudge;
  413. }
  414. protected void paintFocusIndicator(Graphics g, int tabPlacement,
  415. Rectangle[] rects, int tabIndex,
  416. Rectangle iconRect, Rectangle textRect,
  417. boolean isSelected) {
  418. Rectangle tabRect = rects[tabIndex];
  419. if (tabPane.hasFocus() && isSelected) {
  420. int x, y, w, h;
  421. g.setColor(focus);
  422. switch(tabPlacement) {
  423. case LEFT:
  424. x = tabRect.x + 3;
  425. y = tabRect.y + 3;
  426. w = tabRect.width - 5;
  427. h = tabRect.height - 6;
  428. break;
  429. case RIGHT:
  430. x = tabRect.x + 2;
  431. y = tabRect.y + 3;
  432. w = tabRect.width - 5;
  433. h = tabRect.height - 6;
  434. break;
  435. case BOTTOM:
  436. x = tabRect.x + 3;
  437. y = tabRect.y + 2;
  438. w = tabRect.width - 6;
  439. h = tabRect.height - 5;
  440. break;
  441. case TOP:
  442. default:
  443. x = tabRect.x + 3;
  444. y = tabRect.y + 3;
  445. w = tabRect.width - 6;
  446. h = tabRect.height - 5;
  447. }
  448. BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
  449. }
  450. }
  451. /**
  452. * this function draws the border around each tab
  453. * note that this function does now draw the background of the tab.
  454. * that is done elsewhere
  455. */
  456. protected void paintTabBorder(Graphics g, int tabPlacement,
  457. int tabIndex,
  458. int x, int y, int w, int h,
  459. boolean isSelected ) {
  460. g.setColor(lightHighlight);
  461. switch (tabPlacement) {
  462. case LEFT:
  463. g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
  464. g.drawLine(x, y+2, x, y+h-3); // left highlight
  465. g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
  466. g.drawLine(x+2, y, x+w-1, y); // top highlight
  467. g.setColor(shadow);
  468. g.drawLine(x+2, y+h-2, x+w-1, y+h-2); // bottom shadow
  469. g.setColor(darkShadow);
  470. g.drawLine(x+2, y+h-1, x+w-1, y+h-1); // bottom dark shadow
  471. break;
  472. case RIGHT:
  473. g.drawLine(x, y, x+w-3, y); // top highlight
  474. g.setColor(shadow);
  475. g.drawLine(x, y+h-2, x+w-3, y+h-2); // bottom shadow
  476. g.drawLine(x+w-2, y+2, x+w-2, y+h-3); // right shadow
  477. g.setColor(darkShadow);
  478. g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right dark shadow
  479. g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
  480. g.drawLine(x+w-1, y+2, x+w-1, y+h-3); // right dark shadow
  481. g.drawLine(x, y+h-1, x+w-3, y+h-1); // bottom dark shadow
  482. break;
  483. case BOTTOM:
  484. g.drawLine(x, y, x, y+h-3); // left highlight
  485. g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
  486. g.setColor(shadow);
  487. g.drawLine(x+2, y+h-2, x+w-3, y+h-2); // bottom shadow
  488. g.drawLine(x+w-2, y, x+w-2, y+h-3); // right shadow
  489. g.setColor(darkShadow);
  490. g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow
  491. g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
  492. g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow
  493. break;
  494. case TOP:
  495. default:
  496. g.drawLine(x, y+2, x, y+h-1); // left highlight
  497. g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
  498. g.drawLine(x+2, y, x+w-3, y); // top highlight
  499. g.setColor(shadow);
  500. g.drawLine(x+w-2, y+2, x+w-2, y+h-1); // right shadow
  501. g.setColor(darkShadow);
  502. g.drawLine(x+w-1, y+2, x+w-1, y+h-1); // right dark-shadow
  503. g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right shadow
  504. }
  505. }
  506. protected void paintTabBackground(Graphics g, int tabPlacement,
  507. int tabIndex,
  508. int x, int y, int w, int h,
  509. boolean isSelected ) {
  510. g.setColor(tabPane.getBackgroundAt(tabIndex));
  511. switch(tabPlacement) {
  512. case LEFT:
  513. g.fillRect(x+1, y+1, w-2, h-3);
  514. break;
  515. case RIGHT:
  516. g.fillRect(x, y+1, w-2, h-3);
  517. break;
  518. case BOTTOM:
  519. g.fillRect(x+1, y, w-3, h-1);
  520. break;
  521. case TOP:
  522. default:
  523. g.fillRect(x+1, y+1, w-3, h-1);
  524. }
  525. }
  526. protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
  527. int width = tabPane.getWidth();
  528. int height = tabPane.getHeight();
  529. Insets insets = tabPane.getInsets();
  530. int x = insets.left;
  531. int y = insets.top;
  532. int w = width - insets.right - insets.left;
  533. int h = height - insets.top - insets.bottom;
  534. switch(tabPlacement) {
  535. case LEFT:
  536. x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  537. w -= (x - insets.left);
  538. break;
  539. case RIGHT:
  540. w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  541. break;
  542. case BOTTOM:
  543. h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  544. break;
  545. case TOP:
  546. default:
  547. y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  548. h -= (y - insets.top);
  549. }
  550. paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
  551. paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
  552. paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
  553. paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
  554. }
  555. protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
  556. int selectedIndex,
  557. int x, int y, int w, int h) {
  558. g.setColor(lightHighlight);
  559. if (tabPlacement != TOP || selectedIndex < 0 ||
  560. (rects[selectedIndex].y + rects[selectedIndex].height + 1 < y)) {
  561. g.drawLine(x, y, x+w-2, y);
  562. } else {
  563. Rectangle selRect = rects[selectedIndex];
  564. g.drawLine(x, y, selRect.x - 1, y);
  565. if (selRect.x + selRect.width < x + w - 2) {
  566. g.drawLine(selRect.x + selRect.width, y,
  567. x+w-2, y);
  568. } else {
  569. g.setColor(shadow);
  570. g.drawLine(x+w-2, y, x+w-2, y);
  571. }
  572. }
  573. }
  574. protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
  575. int selectedIndex,
  576. int x, int y, int w, int h) {
  577. g.setColor(lightHighlight);
  578. if (tabPlacement != LEFT || selectedIndex < 0 ||
  579. (rects[selectedIndex].x + rects[selectedIndex].width + 1< x)) {
  580. g.drawLine(x, y, x, y+h-2);
  581. } else {
  582. Rectangle selRect = rects[selectedIndex];
  583. g.drawLine(x, y, x, selRect.y - 1);
  584. if (selRect.y + selRect.height < y + h - 2) {
  585. g.drawLine(x, selRect.y + selRect.height,
  586. x, y+h-2);
  587. }
  588. }
  589. }
  590. protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
  591. int selectedIndex,
  592. int x, int y, int w, int h) {
  593. g.setColor(shadow);
  594. if (tabPlacement != BOTTOM || selectedIndex < 0 ||
  595. (rects[selectedIndex].y - 1 > h)) {
  596. g.drawLine(x+1, y+h-2, x+w-2, y+h-2);
  597. g.setColor(darkShadow);
  598. g.drawLine(x, y+h-1, x+w-1, y+h-1);
  599. } else {
  600. Rectangle selRect = rects[selectedIndex];
  601. g.drawLine(x+1, y+h-2, selRect.x - 1, y+h-2);
  602. g.setColor(darkShadow);
  603. g.drawLine(x, y+h-1, selRect.x - 1, y+h-1);
  604. if (selRect.x + selRect.width < x + w - 2) {
  605. g.setColor(shadow);
  606. g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2);
  607. g.setColor(darkShadow);
  608. g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1);
  609. }
  610. }
  611. }
  612. protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
  613. int selectedIndex,
  614. int x, int y, int w, int h) {
  615. g.setColor(shadow);
  616. if (tabPlacement != RIGHT || selectedIndex < 0 ||
  617. rects[selectedIndex].x - 1 > w) {
  618. g.drawLine(x+w-2, y+1, x+w-2, y+h-3);
  619. g.setColor(darkShadow);
  620. g.drawLine(x+w-1, y, x+w-1, y+h-1);
  621. } else {
  622. Rectangle selRect = rects[selectedIndex];
  623. g.drawLine(x+w-2, y+1, x+w-2, selRect.y - 1);
  624. g.setColor(darkShadow);
  625. g.drawLine(x+w-1, y, x+w-1, selRect.y - 1);
  626. if (selRect.y + selRect.height < y + h - 2) {
  627. g.setColor(shadow);
  628. g.drawLine(x+w-2, selRect.y + selRect.height,
  629. x+w-2, y+h-2);
  630. g.setColor(darkShadow);
  631. g.drawLine(x+w-1, selRect.y + selRect.height,
  632. x+w-1, y+h-2);
  633. }
  634. }
  635. }
  636. private void ensureCurrentLayout() {
  637. if (!tabPane.isValid()) {
  638. tabPane.validate();
  639. }
  640. /* If tabPane doesn't have a peer yet, the validate() call will
  641. * silently fail. We handle that by forcing a layout if tabPane
  642. * is still invalid. See bug 4237677.
  643. */
  644. if (!tabPane.isValid()) {
  645. TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
  646. layout.calculateLayoutInfo();
  647. }
  648. }
  649. // TabbedPaneUI methods
  650. public Rectangle getTabBounds(JTabbedPane pane, int i) {
  651. ensureCurrentLayout();
  652. return new Rectangle(rects[i]);
  653. }
  654. public int getTabRunCount(JTabbedPane pane) {
  655. return runCount;
  656. }
  657. public int tabForCoordinate(JTabbedPane pane, int x, int y) {
  658. ensureCurrentLayout();
  659. int tabCount = tabPane.getTabCount();
  660. for (int i = 0; i < tabCount; i++) {
  661. if (rects[i].contains(x, y)) {
  662. return i;
  663. }
  664. }
  665. return -1;
  666. }
  667. // BasicTabbedPaneUI methods
  668. protected Component getVisibleComponent() {
  669. return visibleComponent;
  670. }
  671. protected void setVisibleComponent(Component component) {
  672. if (visibleComponent == component) {
  673. return;
  674. }
  675. if (visibleComponent != null) {
  676. visibleComponent.setVisible(false);
  677. }
  678. if (component != null) {
  679. component.setVisible(true);
  680. }
  681. visibleComponent = component;
  682. }
  683. protected void assureRectsCreated(int tabCount) {
  684. int rectArrayLen = rects.length;
  685. if (tabCount != rectArrayLen ) {
  686. Rectangle[] tempRectArray = new Rectangle[tabCount];
  687. System.arraycopy(rects, 0, tempRectArray, 0,
  688. Math.min(rectArrayLen, tabCount));
  689. rects = tempRectArray;
  690. for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
  691. rects[rectIndex] = new Rectangle();
  692. }
  693. }
  694. }
  695. protected void expandTabRunsArray() {
  696. int rectLen = tabRuns.length;
  697. int[] newArray = new int[rectLen+10];
  698. System.arraycopy(tabRuns, 0, newArray, 0, runCount);
  699. tabRuns = newArray;
  700. }
  701. protected int getRunForTab(int tabCount, int tabIndex) {
  702. for (int i = 0; i < runCount; i++) {
  703. int first = tabRuns[i];
  704. int last = lastTabInRun(tabCount, i);
  705. if (tabIndex >= first && tabIndex <= last) {
  706. return i;
  707. }
  708. }
  709. return 0;
  710. }
  711. protected int lastTabInRun(int tabCount, int run) {
  712. if (runCount == 1) {
  713. return tabCount - 1;
  714. }
  715. int nextRun = (run == runCount - 1? 0 : run + 1);
  716. if (tabRuns[nextRun] == 0) {
  717. return tabCount - 1;
  718. }
  719. return tabRuns[nextRun]-1;
  720. }
  721. protected int getTabRunOverlay(int tabPlacement) {
  722. return tabRunOverlay;
  723. }
  724. protected int getTabRunIndent(int tabPlacement, int run) {
  725. return 0;
  726. }
  727. protected boolean shouldPadTabRun(int tabPlacement, int run) {
  728. return runCount > 1;
  729. }
  730. protected boolean shouldRotateTabRuns(int tabPlacement) {
  731. return true;
  732. }
  733. protected Icon getIconForTab(int tabIndex) {
  734. return ((!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex)) &&
  735. tabPane.getDisabledIconAt(tabIndex) != null)?
  736. tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex);
  737. }
  738. protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
  739. int height = 0;
  740. View v;
  741. if ( (htmlViews!=null) &&
  742. ((v = (View)htmlViews.elementAt(tabIndex)) != null)) {
  743. height += (int)v.getPreferredSpan(View.Y_AXIS);
  744. } else {
  745. height += fontHeight;
  746. }
  747. Icon icon = getIconForTab(tabIndex);
  748. Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
  749. if (icon != null) {
  750. height = Math.max(height, icon.getIconHeight());
  751. }
  752. height += tabInsets.top + tabInsets.bottom + 2;
  753. return height;
  754. }
  755. protected int calculateMaxTabHeight(int tabPlacement) {
  756. FontMetrics metrics = getFontMetrics();
  757. int tabCount = tabPane.getTabCount();
  758. int result = 0;
  759. int fontHeight = metrics.getHeight();
  760. for(int i = 0; i < tabCount; i++) {
  761. result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
  762. }
  763. return result;
  764. }
  765. protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
  766. Icon icon = getIconForTab(tabIndex);
  767. Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
  768. int width = tabInsets.left + tabInsets.right + 3;
  769. View v;
  770. if (icon != null) {
  771. width += icon.getIconWidth() + textIconGap;
  772. }
  773. if ((htmlViews!=null) &&
  774. ((v = (View)htmlViews.elementAt(tabIndex)) != null)) {
  775. width += (int)v.getPreferredSpan(View.X_AXIS);
  776. } else {
  777. String title = tabPane.getTitleAt(tabIndex);
  778. width += SwingUtilities.computeStringWidth(metrics, title);
  779. }
  780. return width;
  781. }
  782. protected int calculateMaxTabWidth(int tabPlacement) {
  783. FontMetrics metrics = getFontMetrics();
  784. int tabCount = tabPane.getTabCount();
  785. int result = 0;
  786. for(int i = 0; i < tabCount; i++) {
  787. result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
  788. }
  789. return result;
  790. }
  791. protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) {
  792. Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
  793. int tabRunOverlay = getTabRunOverlay(tabPlacement);
  794. return (horizRunCount > 0?
  795. horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay +
  796. tabAreaInsets.top + tabAreaInsets.bottom :
  797. 0);
  798. }
  799. protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, int maxTabWidth) {
  800. Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
  801. int tabRunOverlay = getTabRunOverlay(tabPlacement);
  802. return (vertRunCount > 0?
  803. vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay +
  804. tabAreaInsets.left + tabAreaInsets.right :
  805. 0);
  806. }
  807. protected Insets getTabInsets(int tabPlacement, int tabIndex) {
  808. return tabInsets;
  809. }
  810. protected Insets getSelectedTabPadInsets(int tabPlacement) {
  811. rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement);
  812. return currentPadInsets;
  813. }
  814. protected Insets getTabAreaInsets(int tabPlacement) {
  815. rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
  816. return currentTabAreaInsets;
  817. }
  818. protected Insets getContentBorderInsets(int tabPlacement) {
  819. return contentBorderInsets;
  820. }
  821. protected FontMetrics getFontMetrics() {
  822. Font font = tabPane.getFont();
  823. return Toolkit.getDefaultToolkit().getFontMetrics(font);
  824. }
  825. // Tab Navigation methods
  826. protected void navigateSelectedTab(int direction) {
  827. int tabPlacement = tabPane.getTabPlacement();
  828. int current = tabPane.getSelectedIndex();
  829. int tabCount = tabPane.getTabCount();
  830. int offset;
  831. switch(tabPlacement) {
  832. case LEFT:
  833. case RIGHT:
  834. switch(direction) {
  835. case NORTH:
  836. selectPreviousTab(current);
  837. break;
  838. case SOUTH:
  839. selectNextTab(current);
  840. break;
  841. case WEST:
  842. offset = getTabRunOffset(tabPlacement, tabCount, current, false);
  843. selectAdjacentRunTab(tabPlacement, current, offset);
  844. break;
  845. case EAST:
  846. offset = getTabRunOffset(tabPlacement, tabCount, current, true);
  847. selectAdjacentRunTab(tabPlacement, current, offset);
  848. break;
  849. default:
  850. }
  851. break;
  852. case BOTTOM:
  853. case TOP:
  854. default:
  855. switch(direction) {
  856. case NORTH:
  857. offset = getTabRunOffset(tabPlacement, tabCount, current, false);
  858. selectAdjacentRunTab(tabPlacement, current, offset);
  859. break;
  860. case SOUTH:
  861. offset = getTabRunOffset(tabPlacement, tabCount, current, true);
  862. selectAdjacentRunTab(tabPlacement, current, offset);
  863. break;
  864. case EAST:
  865. selectNextTab(current);
  866. break;
  867. case WEST:
  868. selectPreviousTab(current);
  869. break;
  870. default:
  871. }
  872. }
  873. }
  874. protected void selectNextTab(int current) {
  875. int tabIndex = getNextTabIndex(current);
  876. while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
  877. tabIndex = getNextTabIndex(tabIndex);
  878. }
  879. tabPane.setSelectedIndex(tabIndex);
  880. }
  881. protected void selectPreviousTab(int current) {
  882. int tabIndex = getPreviousTabIndex(current);
  883. while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
  884. tabIndex = getPreviousTabIndex(tabIndex);
  885. }
  886. tabPane.setSelectedIndex(tabIndex);
  887. }
  888. protected void selectAdjacentRunTab(int tabPlacement,
  889. int tabIndex, int offset) {
  890. if ( runCount < 2 ) {
  891. return;
  892. }
  893. int newIndex;
  894. Rectangle r = rects[tabIndex];
  895. switch(tabPlacement) {
  896. case LEFT:
  897. case RIGHT:
  898. newIndex = tabForCoordinate(tabPane, r.x + r.width2 + offset,
  899. r.y + r.height2);
  900. break;
  901. case BOTTOM:
  902. case TOP:
  903. default:
  904. newIndex = tabForCoordinate(tabPane, r.x + r.width2,
  905. r.y + r.height2 + offset);
  906. }
  907. if (newIndex != -1) {
  908. while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
  909. newIndex = getNextTabIndex(newIndex);
  910. }
  911. tabPane.setSelectedIndex(newIndex);
  912. }
  913. }
  914. protected int getTabRunOffset(int tabPlacement, int tabCount,
  915. int tabIndex, boolean forward) {
  916. int run = getRunForTab(tabCount, tabIndex);
  917. int offset;
  918. switch(tabPlacement) {
  919. case LEFT: {
  920. if (run == 0) {
  921. offset = (forward?
  922. -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
  923. -maxTabWidth);
  924. } else if (run == runCount - 1) {
  925. offset = (forward?
  926. maxTabWidth :
  927. calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
  928. } else {
  929. offset = (forward? maxTabWidth : -maxTabWidth);
  930. }
  931. break;
  932. }
  933. case RIGHT: {
  934. if (run == 0) {
  935. offset = (forward?
  936. maxTabWidth :
  937. calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
  938. } else if (run == runCount - 1) {
  939. offset = (forward?
  940. -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
  941. -maxTabWidth);
  942. } else {
  943. offset = (forward? maxTabWidth : -maxTabWidth);
  944. }
  945. break;
  946. }
  947. case BOTTOM: {
  948. if (run == 0) {
  949. offset = (forward?
  950. maxTabHeight :
  951. calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
  952. } else if (run == runCount - 1) {
  953. offset = (forward?
  954. -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
  955. -maxTabHeight);
  956. } else {
  957. offset = (forward? maxTabHeight : -maxTabHeight);
  958. }
  959. break;
  960. }
  961. case TOP:
  962. default: {
  963. if (run == 0) {
  964. offset = (forward?
  965. -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
  966. -maxTabHeight);
  967. } else if (run == runCount - 1) {
  968. offset = (forward?
  969. maxTabHeight :
  970. calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
  971. } else {
  972. offset = (forward? maxTabHeight : -maxTabHeight);
  973. }
  974. }
  975. }
  976. return offset;
  977. }
  978. protected int getPreviousTabIndex(int base) {
  979. int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1);
  980. return (tabIndex >= 0? tabIndex : 0);
  981. }
  982. protected int getNextTabIndex(int base) {
  983. return (base+1)%tabPane.getTabCount();
  984. }
  985. protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) {
  986. switch(targetPlacement) {
  987. case LEFT:
  988. targetInsets.top = topInsets.right;
  989. targetInsets.left = topInsets.top;
  990. targetInsets.bottom = topInsets.left;
  991. targetInsets.right = topInsets.bottom;
  992. break;
  993. case BOTTOM:
  994. targetInsets.top = topInsets.bottom;
  995. targetInsets.left = topInsets.right;
  996. targetInsets.bottom = topInsets.top;
  997. targetInsets.right = topInsets.left;
  998. break;
  999. case RIGHT:
  1000. targetInsets.top = topInsets.left;
  1001. targetInsets.left = topInsets.bottom;
  1002. targetInsets.bottom = topInsets.right;
  1003. targetInsets.right = topInsets.top;
  1004. break;
  1005. case TOP:
  1006. default:
  1007. targetInsets.top = topInsets.top;
  1008. targetInsets.left = topInsets.left;
  1009. targetInsets.bottom = topInsets.bottom;
  1010. targetInsets.right = topInsets.right;
  1011. }
  1012. }
  1013. // REMIND(aim,7/29/98): This method should be made
  1014. // protected in the next release where
  1015. // API changes are allowed
  1016. //
  1017. boolean requestFocusForVisibleComponent() {
  1018. Component visibleComponent = getVisibleComponent();
  1019. if (visibleComponent.isFocusTraversable()) {
  1020. visibleComponent.requestFocus();
  1021. return true;
  1022. } else if (visibleComponent instanceof JComponent) {
  1023. if (((JComponent)visibleComponent).requestDefaultFocus()) {
  1024. return true;
  1025. }
  1026. }
  1027. return false;
  1028. }
  1029. // REMIND(aim,7/29/98): These actions should be broken
  1030. // out into protected inner classes in the next release where
  1031. // API changes are allowed
  1032. private static class RightAction extends AbstractAction {
  1033. public void actionPerformed(ActionEvent e) {
  1034. JTabbedPane pane = (JTabbedPane)e.getSource();
  1035. BasicTabbedPaneUI ui = (BasicTabbedPaneUI)pane.getUI();
  1036. ui.navigateSelectedTab(EAST);
  1037. }
  1038. };
  1039. private static class LeftAction extends AbstractAction {
  1040. public void actionPerformed(ActionEvent e) {
  1041. JTabbedPane pane = (JTabbedPane)e.getSource();
  1042. BasicTabbedPaneUI ui = (BasicTabbedPaneUI)pane.getUI();
  1043. ui.navigateSelectedTab(WEST);
  1044. }
  1045. };
  1046. private static class UpAction extends AbstractAction {
  1047. public void actionPerformed(ActionEvent e) {
  1048. JTabbedPane pane = (JTabbedPane)e.getSource();
  1049. BasicTabbedPaneUI ui = (BasicTabbedPaneUI)pane.getUI();
  1050. ui.navigateSelectedTab(NORTH);
  1051. }
  1052. };
  1053. private static class DownAction extends AbstractAction {
  1054. public void actionPerformed(ActionEvent e) {
  1055. JTabbedPane pane = (JTabbedPane)e.getSource();
  1056. BasicTabbedPaneUI ui = (BasicTabbedPaneUI)pane.getUI();
  1057. ui.navigateSelectedTab(SOUTH);
  1058. }
  1059. };
  1060. private static class PageUpAction extends AbstractAction {
  1061. public void actionPerformed(ActionEvent e) {
  1062. JTabbedPane pane = (JTabbedPane)e.getSource();
  1063. BasicTabbedPaneUI ui = (BasicTabbedPaneUI)pane.getUI();
  1064. int tabPlacement = pane.getTabPlacement();
  1065. if (tabPlacement == TOP|| tabPlacement == BOTTOM) {
  1066. ui.navigateSelectedTab(WEST);
  1067. } else {
  1068. ui.navigateSelectedTab(NORTH);
  1069. }
  1070. }
  1071. };
  1072. private static class PageDownAction extends AbstractAction {
  1073. public void actionPerformed(ActionEvent e) {
  1074. JTabbedPane pane = (JTabbedPane)e.getSource();
  1075. BasicTabbedPaneUI ui = (BasicTabbedPaneUI)pane.getUI();
  1076. int tabPlacement = pane.getTabPlacement();
  1077. if (tabPlacement == TOP || tabPlacement == BOTTOM) {
  1078. ui.navigateSelectedTab(EAST);
  1079. } else {
  1080. ui.navigateSelectedTab(SOUTH);
  1081. }
  1082. }
  1083. };
  1084. private static class RequestFocusAction extends AbstractAction {
  1085. public void actionPerformed(ActionEvent e) {
  1086. JTabbedPane pane = (JTabbedPane)e.getSource();
  1087. pane.requestFocus();
  1088. }
  1089. };
  1090. private static class RequestFocusForVisibleAction extends AbstractAction {
  1091. public void actionPerformed(ActionEvent e) {
  1092. JTabbedPane pane = (JTabbedPane)e.getSource();
  1093. BasicTabbedPaneUI ui = (BasicTabbedPaneUI)pane.getUI();
  1094. ui.requestFocusForVisibleComponent();
  1095. }
  1096. };
  1097. /**
  1098. * This inner class is marked "public" due to a compiler bug.
  1099. * This class should be treated as a "protected" inner class.
  1100. * Instantiate it only within subclasses of BasicTabbedPaneUI.
  1101. */
  1102. public class TabbedPaneLayout implements LayoutManager {
  1103. public void addLayoutComponent(String name, Component comp) {}
  1104. public void removeLayoutComponent(Component comp) {}
  1105. public Dimension preferredLayoutSize(Container parent) {
  1106. return calculateSize(false);
  1107. }
  1108. public Dimension minimumLayoutSize(Container parent) {
  1109. return calculateSize(true);
  1110. }
  1111. protected Dimension calculateSize(boolean minimum) {
  1112. int tabPlacement = tabPane.getTabPlacement();
  1113. Insets insets = tabPane.getInsets();
  1114. Insets borderInsets = getContentBorderInsets(tabPlacement);
  1115. Dimension zeroSize = new Dimension(0,0);
  1116. int height = borderInsets.top + borderInsets.bottom;
  1117. int width = borderInsets.left + borderInsets.right;
  1118. int cWidth = 0;
  1119. int cHeight = 0;
  1120. for (int i = 0; i < tabPane.getTabCount(); i++) {
  1121. Component component = tabPane.getComponentAt(i);
  1122. Dimension size = zeroSize;
  1123. size = minimum? component.getMinimumSize() :
  1124. component.getPreferredSize();
  1125. if (size != null) {
  1126. cHeight = Math.max(size.height, cHeight);
  1127. cWidth = Math.max(size.width, cWidth);
  1128. }
  1129. }
  1130. width += cWidth;
  1131. height += cHeight;
  1132. int tabExtent = 0;
  1133. switch(tabPlacement) {
  1134. case LEFT:
  1135. case RIGHT:
  1136. tabExtent = preferredTabAreaWidth(tabPlacement, height);
  1137. width += tabExtent;
  1138. break;
  1139. case TOP:
  1140. case BOTTOM:
  1141. default:
  1142. tabExtent = preferredTabAreaHeight(tabPlacement, width);
  1143. height += tabExtent;
  1144. }
  1145. return new Dimension(width + insets.left + insets.right,
  1146. height + insets.bottom + insets.top);
  1147. }
  1148. protected int preferredTabAreaHeight(int tabPlacement, int width) {
  1149. FontMetrics metrics = getFontMetrics();
  1150. int tabCount = tabPane.getTabCount();
  1151. int total = 0;
  1152. if (tabCount > 0) {
  1153. int rows = 1;
  1154. int x = 0;
  1155. int maxTabHeight = calculateMaxTabHeight(tabPlacement);
  1156. for (int i = 0; i < tabCount; i++) {
  1157. int tabWidth = calculateTabWidth(tabPlacement, i, metrics);
  1158. if (x != 0 && x + tabWidth > width) {
  1159. rows++;
  1160. x = 0;
  1161. }
  1162. x += tabWidth;
  1163. }
  1164. total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
  1165. }
  1166. return total;
  1167. }
  1168. protected int preferredTabAreaWidth(int tabPlacement, int height) {
  1169. FontMetrics metrics = getFontMetrics();
  1170. int tabCount = tabPane.getTabCount();
  1171. int total = 0;
  1172. if (tabCount > 0) {
  1173. int columns = 1;
  1174. int y = 0;
  1175. int fontHeight = metrics.getHeight();
  1176. maxTabWidth = calculateMaxTabWidth(tabPlacement);
  1177. for (int i = 0; i < tabCount; i++) {
  1178. int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
  1179. if (y != 0 && y + tabHeight > height) {
  1180. columns++;
  1181. y = 0;
  1182. }
  1183. y += tabHeight;
  1184. }
  1185. total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
  1186. }
  1187. return total;
  1188. }
  1189. public void layoutContainer(Container parent) {
  1190. int tabPlacement = tabPane.getTabPlacement();
  1191. Insets insets = tabPane.getInsets();
  1192. int selectedIndex = tabPane.getSelectedIndex();
  1193. Component visibleComponent = getVisibleComponent();
  1194. calculateLayoutInfo();
  1195. if (selectedIndex < 0) {
  1196. if (visibleComponent != null) {
  1197. // The last tab was removed, so remove the component
  1198. setVisibleComponent(null);
  1199. }
  1200. } else {
  1201. int cx, cy, cw, ch;
  1202. int totalTabWidth = 0;
  1203. int totalTabHeight = 0;
  1204. Insets borderInsets = getContentBorderInsets(tabPlacement);
  1205. Component selectedComponent = tabPane.getComponentAt(selectedIndex);
  1206. boolean shouldChangeFocus = false;
  1207. // In order to allow programs to use a single component
  1208. // as the display for multiple tabs, we will not change
  1209. // the visible compnent if the currently selected tab
  1210. // has a null component. This is a bit dicey, as we don't
  1211. // explicitly state we support this in the spec, but since
  1212. // programs are now depending on this, we're making it work.
  1213. //
  1214. if (selectedComponent != null) {
  1215. if (selectedComponent != visibleComponent) {
  1216. if (visibleComponent != null) {
  1217. if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
  1218. shouldChangeFocus = true;
  1219. }
  1220. }
  1221. setVisibleComponent(selectedComponent);
  1222. }
  1223. }
  1224. Rectangle bounds = tabPane.getBounds();
  1225. int numChildren = tabPane.getComponentCount();
  1226. if (numChildren > 0) {
  1227. switch(tabPlacement) {
  1228. case LEFT:
  1229. totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  1230. cx = insets.left + totalTabWidth + borderInsets.left;
  1231. cy = insets.top + borderInsets.top;
  1232. break;
  1233. case RIGHT:
  1234. totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  1235. cx = insets.left + borderInsets.left;
  1236. cy = insets.top + borderInsets.top;
  1237. break;
  1238. case BOTTOM:
  1239. totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  1240. cx = insets.left + borderInsets.left;
  1241. cy = insets.top + borderInsets.top;
  1242. break;
  1243. case TOP:
  1244. default:
  1245. totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  1246. cx = insets.left + borderInsets.left;
  1247. cy = insets.top + totalTabHeight + borderInsets.top;
  1248. }
  1249. cw = bounds.width - totalTabWidth -
  1250. insets.left - insets.right -
  1251. borderInsets.left - borderInsets.right;
  1252. ch = bounds.height - totalTabHeight -
  1253. insets.top - insets.bottom -
  1254. borderInsets.top - borderInsets.bottom;
  1255. for (int i=0; i < numChildren; i++) {
  1256. Component child = tabPane.getComponent(i);
  1257. child.setBounds(cx, cy, cw, ch);
  1258. }
  1259. }
  1260. if (shouldChangeFocus) {
  1261. if (!requestFocusForVisibleComponent()) {
  1262. tabPane.requestFocus();
  1263. }
  1264. }
  1265. }
  1266. }
  1267. public void calculateLayoutInfo() {
  1268. int tabCount = tabPane.getTabCount();
  1269. assureRectsCreated(tabCount);
  1270. calculateTabRects(tabPane.getTabPlacement(), tabCount);
  1271. }
  1272. protected void calculateTabRects(int tabPlacement, int tabCount) {
  1273. FontMetrics metrics = getFontMetrics();
  1274. Dimension size = tabPane.getSize();
  1275. Insets insets = tabPane.getInsets();
  1276. Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
  1277. int fontHeight = metrics.getHeight();
  1278. int selectedIndex = tabPane.getSelectedIndex();
  1279. int tabRunOverlay;
  1280. int i, j;
  1281. int x, y;
  1282. int returnAt;
  1283. boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
  1284. boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
  1285. //
  1286. // Calculate bounds within which a tab run must fit
  1287. //
  1288. switch(tabPlacement) {
  1289. case LEFT:
  1290. maxTabWidth = calculateMaxTabWidth(tabPlacement);
  1291. x = insets.left + tabAreaInsets.left;
  1292. y = insets.top + tabAreaInsets.top;
  1293. returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
  1294. break;
  1295. case RIGHT:
  1296. maxTabWidth = calculateMaxTabWidth(tabPlacement);
  1297. x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
  1298. y = insets.top + tabAreaInsets.top;
  1299. returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
  1300. break;
  1301. case BOTTOM:
  1302. maxTabHeight = calculateMaxTabHeight(tabPlacement);
  1303. x = insets.left + tabAreaInsets.left;
  1304. y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
  1305. returnAt = size.width - (insets.right + tabAreaInsets.right);
  1306. break;
  1307. case TOP:
  1308. default:
  1309. maxTabHeight = calculateMaxTabHeight(tabPlacement);
  1310. x = insets.left + tabAreaInsets.left;
  1311. y = insets.top + tabAreaInsets.top;
  1312. returnAt = size.width - (insets.right + tabAreaInsets.right);
  1313. break;
  1314. }
  1315. tabRunOverlay = getTabRunOverlay(tabPlacement);
  1316. runCount = 0;
  1317. selectedRun = -1;
  1318. if (tabCount == 0) {
  1319. return;
  1320. }
  1321. // Run through tabs and partition them into runs
  1322. Rectangle rect;
  1323. for (i = 0; i < tabCount; i++) {
  1324. rect = rects[i];
  1325. if (!verticalTabRuns) {
  1326. // Tabs on TOP or BOTTOM....
  1327. if (i > 0) {
  1328. rect.x = rects[i-1].x + rects[i-1].width;
  1329. } else {
  1330. tabRuns[0] = 0;
  1331. runCount = 1;
  1332. maxTabWidth = 0;
  1333. rect.x = x;
  1334. }
  1335. rect.width = calculateTabWidth(tabPlacement, i, metrics);
  1336. maxTabWidth = Math.max(maxTabWidth, rect.width);
  1337. // Never move a TAB down a run if it is in the first column.
  1338. // Even if there isn't enough room, moving it to a fresh
  1339. // line won't help.
  1340. if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) {
  1341. if (runCount > tabRuns.length - 1) {
  1342. expandTabRunsArray();
  1343. }
  1344. tabRuns[runCount] = i;
  1345. runCount++;
  1346. rect.x = x;
  1347. }
  1348. // Initialize y position in case there's just one run
  1349. rect.y = y;
  1350. rect.height = maxTabHeight/* - 2*/;
  1351. } else {
  1352. // Tabs on LEFT or RIGHT...
  1353. if (i > 0) {
  1354. rect.y = rects[i-1].y + rects[i-1].height;
  1355. } else {
  1356. tabRuns[0] = 0;
  1357. runCount = 1;
  1358. maxTabHeight = 0;
  1359. rect.y = y;
  1360. }
  1361. rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
  1362. maxTabHeight = Math.max(maxTabHeight, rect.height);
  1363. // Never move a TAB over a run if it is in the first run.
  1364. // Even if there isn't enough room, moving it to a fresh
  1365. // column won't help.
  1366. if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
  1367. if (runCount > tabRuns.length - 1) {
  1368. expandTabRunsArray();
  1369. }
  1370. tabRuns[runCount] = i;
  1371. runCount++;
  1372. rect.y = y;
  1373. }
  1374. // Initialize x position in case there's just one column
  1375. rect.x = x;
  1376. rect.width = maxTabWidth/* - 2*/;
  1377. }
  1378. if (i == selectedIndex) {
  1379. selectedRun = runCount - 1;
  1380. }
  1381. }
  1382. if (runCount > 1) {
  1383. // Re-distribute tabs in case last run has leftover space
  1384. normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
  1385. selectedRun = getRunForTab(tabCount, selectedIndex);
  1386. // Rotate run array so that selected run is first
  1387. if (shouldRotateTabRuns(tabPlacement)) {
  1388. rotateTabRuns(tabPlacement, selectedRun);
  1389. }
  1390. }
  1391. // Step through runs from back to front to calculate
  1392. // tab y locations and to pad runs appropriately
  1393. for (i = runCount - 1; i >= 0; i--) {
  1394. int start = tabRuns[i];
  1395. int next = tabRuns[i == (runCount - 1)? 0 : i + 1];
  1396. int end = (next != 0? next - 1 : tabCount - 1);
  1397. if (!verticalTabRuns) {
  1398. for (j = start; j <= end; j++) {
  1399. rect = rects[j];
  1400. rect.y = y;
  1401. rect.x += getTabRunIndent(tabPlacement, i);
  1402. }
  1403. if (shouldPadTabRun(tabPlacement, i)) {
  1404. padTabRun(tabPlacement, start, end, returnAt);
  1405. }
  1406. if (tabPlacement == BOTTOM) {
  1407. y -= (maxTabHeight - tabRunOverlay);
  1408. } else {
  1409. y += (maxTabHeight - tabRunOverlay);
  1410. }
  1411. } else {
  1412. for (j = start; j <= end; j++) {
  1413. rect = rects[j];
  1414. rect.x = x;
  1415. rect.y += getTabRunIndent(tabPlacement, i);
  1416. }
  1417. if (shouldPadTabRun(tabPlacement, i)) {
  1418. padTabRun(tabPlacement, start, end, returnAt);
  1419. }
  1420. if (tabPlacement == RIGHT) {
  1421. x -= (maxTabWidth - tabRunOverlay);
  1422. } else {
  1423. x += (maxTabWidth - tabRunOverlay);
  1424. }
  1425. }
  1426. }
  1427. // Pad the selected tab so that it appears raised in front
  1428. padSelectedTab(tabPlacement, selectedIndex);
  1429. // if right to left and tab placement on the top or
  1430. // the bottom, flip x positions and adjust by widths
  1431. if (!leftToRight && !verticalTabRuns) {
  1432. int rightMargin = size.width
  1433. - (insets.right + tabAreaInsets.right);
  1434. for (i = 0; i < tabCount; i++) {
  1435. rects[i].x = rightMargin - rects[i].x - rects[i].width;
  1436. }
  1437. }
  1438. }
  1439. /*
  1440. * Rotates the run-index array so that the selected run is run[0]
  1441. */
  1442. protected void rotateTabRuns(int tabPlacement, int selectedRun) {
  1443. for (int i = 0; i < selectedRun; i++) {
  1444. int save = tabRuns[0];
  1445. for (int j = 1; j < runCount; j++) {
  1446. tabRuns[j - 1] = tabRuns[j];
  1447. }
  1448. tabRuns[runCount-1] = save;
  1449. }
  1450. }
  1451. protected void normalizeTabRuns(int tabPlacement, int tabCount,
  1452. int start, int max) {
  1453. boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
  1454. int run = runCount - 1;
  1455. boolean keepAdjusting = true;
  1456. double weight = 1.25;
  1457. // At this point the tab runs are packed to fit as many
  1458. // tabs as possible, which can leave the last run with a lot
  1459. // of extra space (resulting in very fat tabs on the last run).
  1460. // So we'll attempt to distribute this extra space more evenly
  1461. // across the runs in order to make the runs look more consistent.
  1462. //
  1463. // Starting with the last run, determine whether the last tab in
  1464. // the previous run would fit (generously) in this run; if so,
  1465. // move tab to current run and shift tabs accordingly. Cycle
  1466. // through remaining runs using the same algorithm.
  1467. //
  1468. while (keepAdjusting) {
  1469. int last = lastTabInRun(tabCount, run);
  1470. int prevLast = lastTabInRun(tabCount, run-1);
  1471. int end;
  1472. int prevLastLen;
  1473. if (!verticalTabRuns) {
  1474. end = rects[last].x + rects[last].width;
  1475. prevLastLen = (int)(maxTabWidth*weight);
  1476. } else {
  1477. end = rects[last].y + rects[last].height;
  1478. prevLastLen = (int)(maxTabHeight*weight*2);
  1479. }
  1480. // Check if the run has enough extra space to fit the last tab
  1481. // from the previous row...
  1482. if (max - end > prevLastLen) {
  1483. // Insert tab from previous row and shift rest over
  1484. tabRuns[run] = prevLast;
  1485. if (!verticalTabRuns) {
  1486. rects[prevLast].x = start;
  1487. } else {
  1488. rects[prevLast].y = start;
  1489. }
  1490. for (int i = prevLast+1; i <= last; i++) {
  1491. if (!verticalTabRuns) {
  1492. rects[i].x = rects[i-1].x + rects[i-1].width;
  1493. } else {
  1494. rects[i].y = rects[i-1].y + rects[i-1].height;
  1495. }
  1496. }
  1497. } else if (run == runCount - 1) {
  1498. // no more room left in last run, so we're done!
  1499. keepAdjusting = false;
  1500. }
  1501. if (run - 1 > 0) {
  1502. // check previous run next...
  1503. run -= 1;
  1504. } else {
  1505. // check last run again...but require a higher ratio
  1506. // of extraspace-to-tabsize because we don't want to
  1507. // end up with too many tabs on the last run!
  1508. run = runCount - 1;
  1509. weight += .25;
  1510. }
  1511. }
  1512. }
  1513. protected void padTabRun(int tabPlacement, int start, int end, int max) {
  1514. Rectangle lastRect = rects[end];
  1515. if (tabPlacement == TOP || tabPlacement == BOTTOM) {
  1516. int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
  1517. int deltaWidth = max - (lastRect.x + lastRect.width);
  1518. float factor = (float)deltaWidth / (float)runWidth;
  1519. for (int j = start; j <= end; j++) {
  1520. Rectangle pastRect = rects[j];
  1521. if (j > start) {
  1522. pastRect.x = rects[j-1].x + rects[j-1].width;
  1523. }
  1524. pastRect.width += Math.round((float)pastRect.width * factor);
  1525. }
  1526. lastRect.width = max - lastRect.x;
  1527. } else {
  1528. int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
  1529. int deltaHeight = max - (lastRect.y + lastRect.height);
  1530. float factor = (float)deltaHeight / (float)runHeight;
  1531. for (int j = start; j <= end; j++) {
  1532. Rectangle pastRect = rects[j];
  1533. if (j > start) {
  1534. pastRect.y = rects[j-1].y + rects[j-1].height;
  1535. }
  1536. pastRect.height += Math.round((float)pastRect.height * factor);
  1537. }
  1538. lastRect.height = max - lastRect.y;
  1539. }
  1540. }
  1541. protected void padSelectedTab(int tabPlacement, int selectedIndex) {
  1542. if (selectedIndex >= 0) {
  1543. Rectangle selRect = rects[selectedIndex];
  1544. Insets padInsets = getSelectedTabPadInsets(tabPlacement);
  1545. selRect.x -= padInsets.left;
  1546. selRect.width += (padInsets.left + padInsets.right);
  1547. selRect.y -= padInsets.top;
  1548. selRect.height += (padInsets.top + padInsets.bottom);
  1549. }
  1550. }
  1551. }
  1552. // Controller: event listeners
  1553. /**
  1554. * This inner class is marked "public" due to a compiler bug.
  1555. * This class should be treated as a "protected" inner class.
  1556. * Instantiate it only within subclasses of BasicTabbedPaneUI.
  1557. */
  1558. public class PropertyChangeHandler implements PropertyChangeListener {
  1559. public void propertyChange(PropertyChangeEvent e) {
  1560. }
  1561. }
  1562. /**
  1563. * This inner class is marked "public" due to a compiler bug.
  1564. * This class should be treated as a "protected" inner class.
  1565. * Instantiate it only within subclasses of BasicTabbedPaneUI.
  1566. */
  1567. public class TabSelectionHandler implements ChangeListener {
  1568. public void stateChanged(ChangeEvent e) {
  1569. JTabbedPane tabPane = (JTabbedPane)e.getSource();
  1570. tabPane.revalidate();
  1571. tabPane.repaint();
  1572. }
  1573. }
  1574. /**
  1575. * This inner class is marked "public" due to a compiler bug.
  1576. * This class should be treated as a "protected" inner class.
  1577. * Instantiate it only within subclasses of BasicTabbedPaneUI.
  1578. */
  1579. public class MouseHandler extends MouseAdapter {
  1580. public void mousePressed(MouseEvent e) {
  1581. JTabbedPane tabPane = (JTabbedPane)e.getSource();
  1582. if (!tabPane.isEnabled()) {
  1583. return;
  1584. }
  1585. int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
  1586. if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
  1587. if (tabIndex == tabPane.getSelectedIndex()) {
  1588. tabPane.requestFocus();
  1589. tabPane.repaint(rects[tabIndex]);
  1590. } else {
  1591. tabPane.setSelectedIndex(tabIndex);
  1592. }
  1593. }
  1594. }
  1595. }
  1596. /**
  1597. * This inner class is marked "public" due to a compiler bug.
  1598. * This class should be treated as a "protected" inner class.
  1599. * Instantiate it only within subclasses of BasicTabbedPaneUI.
  1600. */
  1601. public class FocusHandler extends FocusAdapter {
  1602. public void focusGained(FocusEvent e) {
  1603. JTabbedPane tabPane = (JTabbedPane)e.getSource();
  1604. // To get around bug in JDK1.1.5 where FocusEvents are
  1605. // not properly posted asynchronously to the queue
  1606. int tabCount = tabPane.getTabCount();
  1607. if (tabCount > 0 && tabCount == rects.length) {
  1608. tabPane.repaint(rects[tabPane.getSelectedIndex()]);
  1609. }
  1610. }
  1611. public void focusLost(FocusEvent e) {
  1612. JTabbedPane tabPane = (JTabbedPane)e.getSource();
  1613. // To get around bug in JDK1.1.5 where FocusEvents are
  1614. // not properly posted asynchronously to the queue
  1615. int tabCount = tabPane.getTabCount();
  1616. if (tabCount > 0 && tabCount == rects.length) {
  1617. tabPane.repaint(rects[tabPane.getSelectedIndex()]);
  1618. }
  1619. }
  1620. }
  1621. /* GES 2/3/99:
  1622. The container listener code was added to support HTML
  1623. rendering of tab titles.
  1624. Ideally, we would be able to listen for property changes
  1625. when a tab is added or its text modified. At the moment
  1626. there are no such events because the Beans spec doesn't
  1627. allow 'indexed' property changes (i.e. tab 2's text changed
  1628. from A to B).
  1629. In order to get around this, we listen for tabs to be added
  1630. or removed by listening for the container events. we then
  1631. queue up a runnable (so the component has a chance to complete
  1632. the add) which checks the tab title of the new component to see
  1633. if it requires HTML rendering.
  1634. The Views (one per tab title requiring HTML rendering) are
  1635. stored in the htmlViews Vector, which is only allocated after
  1636. the first time we run into an HTML tab. Note that this vector
  1637. is kept in step with the number of pages, and nulls are added
  1638. for those pages whose tab title do not require HTML rendering.
  1639. This makes it easy for the paint and layout code to tell
  1640. whether to invoke the HTML engine without having to check
  1641. the string during time-sensitive operations.
  1642. When we have added a way to listen for tab additions and
  1643. changes to tab text, this code should be removed and
  1644. replaced by something which uses that. */
  1645. private class ContainerHandler implements ContainerListener {
  1646. public void componentAdded(ContainerEvent e) {
  1647. JTabbedPane tp = (JTabbedPane)e.getContainer();
  1648. Component child = e.getChild();
  1649. int index = tp.indexOfComponent(child);
  1650. String title = tp.getTitleAt(index);
  1651. boolean isHTML = BasicHTML.isHTMLString(title);
  1652. if (isHTML) {
  1653. if (htmlViews==null) { // Initialize vector
  1654. htmlViews = createHTMLVector();
  1655. } else { // Vector already exists
  1656. View v = BasicHTML.createHTMLView(tp, title);
  1657. htmlViews.insertElementAt(v, index);
  1658. }
  1659. } else { // Not HTML
  1660. if (htmlViews!=null) { // Add placeholder
  1661. htmlViews.insertElementAt(null, index);
  1662. } // else nada!
  1663. }
  1664. }
  1665. public void componentRemoved(ContainerEvent e) {
  1666. JTabbedPane tp = (JTabbedPane)e.getContainer();
  1667. Component child = e.getChild();
  1668. int index = tp.indexOfComponent(child);
  1669. if (htmlViews != null && htmlViews.size()>=index) {
  1670. htmlViews.removeElementAt(index);
  1671. }
  1672. }
  1673. }
  1674. private Vector createHTMLVector() {
  1675. Vector htmlViews = new Vector();
  1676. int count = tabPane.getTabCount();
  1677. if (count>0) {
  1678. for (int i=0 ; i<count; i++) {
  1679. String title = tabPane.getTitleAt(i);
  1680. if (BasicHTML.isHTMLString(title)) {
  1681. htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
  1682. } else {
  1683. htmlViews.addElement(null);
  1684. }
  1685. }
  1686. }
  1687. return htmlViews;
  1688. }
  1689. }