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