1. /*
  2. * @(#)SynthTabbedPaneUI.java 1.25 03/05/08
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.java.swing.plaf.gtk;
  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. import java.util.Hashtable;
  18. // PENDING: nuke this
  19. import javax.swing.plaf.basic.BasicHTML;
  20. /**
  21. * A Basic L&F implementation of TabbedPaneUI.
  22. *
  23. * @version 1.25, 05/08/03 (based on BasicTabbedPaneUI v 1.123)
  24. * @author Amy Fowler
  25. * @author Philip Milne
  26. * @author Steve Wilson
  27. * @author Tom Santos
  28. * @author Dave Moore
  29. */
  30. /**
  31. * Looks up 'selectedTabPadInsets' from the Style, which will be additional
  32. * insets for the selected tab.
  33. */
  34. class SynthTabbedPaneUI extends TabbedPaneUI implements SynthUI, SwingConstants {
  35. private static Action scrollTabsForwardAction =
  36. new ScrollTabsForwardAction();
  37. private static Action scrollTabsBackwardAction =
  38. new ScrollTabsBackwardAction();
  39. // Instance variables initialized at installation
  40. // PENDING: These should be fetched as necessary, eg not maintained as
  41. // fields but passed around.
  42. private SynthContext tabAreaContext;
  43. private TabContext tabContext;
  44. private SynthContext tabContentContext;
  45. private SynthStyle style;
  46. private SynthStyle tabStyle;
  47. private SynthStyle tabAreaStyle;
  48. private SynthStyle tabContentStyle;
  49. private int tabRunOverlay;
  50. protected JTabbedPane tabPane;
  51. // Transient variables (recalculated each time TabbedPane is layed out)
  52. protected int tabRuns[] = new int[10];
  53. protected int runCount = 0;
  54. protected int selectedRun = -1;
  55. protected Rectangle rects[] = new Rectangle[0];
  56. protected int maxTabHeight;
  57. protected int maxTabWidth;
  58. // Listeners
  59. protected ChangeListener tabChangeListener;
  60. protected PropertyChangeListener propertyChangeListener;
  61. protected MouseInputListener mouseInputListener;
  62. protected FocusListener focusListener;
  63. // PENDING(api): See comment for ContainerHandler
  64. private ContainerListener containerListener;
  65. // Private instance data
  66. private Insets currentPadInsets = new Insets(0,0,0,0);
  67. private Insets currentTabAreaInsets = new Insets(0,0,0,0);
  68. private Component visibleComponent;
  69. // PENDING(api): See comment for ContainerHandler
  70. private Vector htmlViews;
  71. private Hashtable mnemonicToIndexMap;
  72. /**
  73. * InputMap used for mnemonics. Only non-null if the JTabbedPane has
  74. * mnemonics associated with it. Lazily created in initMnemonics.
  75. */
  76. private InputMap mnemonicInputMap;
  77. // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
  78. private ScrollableTabSupport tabScroller;
  79. /**
  80. * A rectangle used for general layout calculations in order
  81. * to avoid constructing many new Rectangles on the fly.
  82. */
  83. protected transient Rectangle calcRect = new Rectangle(0,0,0,0);
  84. /**
  85. * Number of tabs. When the count differs, the mnemonics are updated.
  86. */
  87. // PENDING: This wouldn't be necessary if JTabbedPane had a better
  88. // way of notifying listeners when the count changed.
  89. private int tabCount;
  90. /**
  91. * Index of the tab the mouse is over.
  92. */
  93. private int mouseIndex;
  94. /**
  95. * Index of the tab that has focus.
  96. */
  97. private boolean selectionFollowsFocus = true;
  98. private int focusIndex = -1;
  99. /**
  100. * Bounds of the tab under the mouse.
  101. */
  102. private Rectangle mouseBounds;
  103. // UI creation
  104. public static ComponentUI createUI(JComponent c) {
  105. return new SynthTabbedPaneUI();
  106. }
  107. public static void loadActionMap(ActionMap map) {
  108. // NOTE: this needs to remain static. If you have a need to
  109. // have Actions that reference the UI in the ActionMap,
  110. // then you'll also need to change the registeration of the
  111. // ActionMap.
  112. map.put("navigateNext", new NextAction());
  113. map.put("navigatePrevious", new PreviousAction());
  114. map.put("navigateRight", new RightAction());
  115. map.put("navigateLeft", new LeftAction());
  116. map.put("navigateUp", new UpAction());
  117. map.put("navigateDown", new DownAction());
  118. map.put("navigatePageUp", new PageUpAction());
  119. map.put("navigatePageDown", new PageDownAction());
  120. map.put("requestFocus", new RequestFocusAction());
  121. map.put("requestFocusForVisibleComponent",
  122. new RequestFocusForVisibleAction());
  123. map.put("setSelectedIndex", new SetSelectedIndexAction());
  124. map.put("selectTabWithFocus", new SelectFocusIndexAction());
  125. map.put("scrollTabsForwardAction", scrollTabsForwardAction);
  126. map.put("scrollTabsBackwardAction", scrollTabsBackwardAction);
  127. }
  128. // UI Installation/De-installation
  129. public void installUI(JComponent c) {
  130. this.tabPane = (JTabbedPane)c;
  131. c.setLayout(createLayoutManager());
  132. installComponents();
  133. installDefaults();
  134. installListeners();
  135. installKeyboardActions();
  136. }
  137. public void uninstallUI(JComponent c) {
  138. uninstallKeyboardActions();
  139. uninstallListeners();
  140. uninstallDefaults();
  141. uninstallComponents();
  142. c.setLayout(null);
  143. this.tabPane = null;
  144. }
  145. /**
  146. * Invoked by <code>installUI</code> to create
  147. * a layout manager object to manage
  148. * the <code>JTabbedPane</code>.
  149. *
  150. * @return a layout manager object
  151. *
  152. * @see TabbedPaneLayout
  153. * @see javax.swing.JTabbedPane#getTabLayoutPolicy
  154. */
  155. protected LayoutManager createLayoutManager() {
  156. if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
  157. return new TabbedPaneScrollLayout();
  158. } else { /* WRAP_TAB_LAYOUT */
  159. return new TabbedPaneLayout();
  160. }
  161. }
  162. /* In an attempt to preserve backward compatibility for programs
  163. * which have extended BasicTabbedPaneUI to do their own layout, the
  164. * UI uses the installed layoutManager (and not tabLayoutPolicy) to
  165. * determine if scrollTabLayout is enabled.
  166. */
  167. private boolean scrollableTabLayoutEnabled() {
  168. return (tabPane.getLayout() instanceof TabbedPaneScrollLayout);
  169. }
  170. /**
  171. * Creates and installs any required subcomponents for the JTabbedPane.
  172. * Invoked by installUI.
  173. *
  174. * @since 1.4
  175. */
  176. protected void installComponents() {
  177. if (scrollableTabLayoutEnabled()) {
  178. if (tabScroller == null) {
  179. tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
  180. tabPane.add(tabScroller.viewport);
  181. tabPane.add(tabScroller.scrollForwardButton);
  182. tabPane.add(tabScroller.scrollBackwardButton);
  183. }
  184. }
  185. }
  186. /**
  187. * Removes any installed subcomponents from the JTabbedPane.
  188. * Invoked by uninstallUI.
  189. *
  190. * @since 1.4
  191. */
  192. protected void uninstallComponents() {
  193. if (scrollableTabLayoutEnabled()) {
  194. tabPane.remove(tabScroller.viewport);
  195. tabPane.remove(tabScroller.scrollForwardButton);
  196. tabPane.remove(tabScroller.scrollBackwardButton);
  197. tabScroller = null;
  198. }
  199. }
  200. protected void installDefaults() {
  201. fetchStyle(tabPane);
  202. }
  203. private void fetchStyle(JTabbedPane c) {
  204. SynthContext context = getContext(c, ENABLED);
  205. SynthStyle oldStyle = style;
  206. style = SynthLookAndFeel.updateStyle(context, this);
  207. // Add properties other than JComponent colors, Borders and
  208. // opacity settings here:
  209. if (style != oldStyle) {
  210. tabRunOverlay = 0;
  211. Integer value = (Integer)context.getStyle().get(
  212. context, "tabRunOverlay");
  213. if (value != null) {
  214. tabRunOverlay = value.intValue();
  215. }
  216. selectionFollowsFocus = context.getStyle().getBoolean(context,
  217. "TabbedPane.selectionFollowsFocus",
  218. true);
  219. }
  220. context.dispose();
  221. if (tabContext != null) {
  222. tabContext.dispose();
  223. }
  224. tabContext = (TabContext)getContext(c, Region.TABBED_PANE_TAB,
  225. ENABLED);
  226. this.tabStyle = SynthLookAndFeel.updateStyle(tabContext, this);
  227. if (tabAreaContext != null) {
  228. tabAreaContext.dispose();
  229. }
  230. tabAreaContext = getContext(c, Region.TABBED_PANE_TAB_AREA, ENABLED);
  231. this.tabAreaStyle = SynthLookAndFeel.updateStyle(tabAreaContext, this);
  232. if (tabContentContext != null) {
  233. tabContentContext.dispose();
  234. }
  235. tabContentContext = getContext(c, Region.TABBED_PANE_CONTENT, ENABLED);
  236. this.tabContentStyle = SynthLookAndFeel.updateStyle(tabContentContext,
  237. this);
  238. }
  239. protected void uninstallDefaults() {
  240. SynthContext context = getContext(tabPane, ENABLED);
  241. style.uninstallDefaults(context);
  242. context.dispose();
  243. style = null;
  244. context = getContext(tabPane, Region.TABBED_PANE_TAB, ENABLED);
  245. tabStyle.uninstallDefaults(context);
  246. context.dispose();
  247. tabStyle = null;
  248. context = getContext(tabPane,Region.TABBED_PANE_TAB_AREA, ENABLED);
  249. tabAreaStyle.uninstallDefaults(context);
  250. context.dispose();
  251. tabAreaStyle = null;
  252. context = getContext(tabPane, Region.TABBED_PANE_CONTENT, ENABLED);
  253. tabContentStyle.uninstallDefaults(context);
  254. context.dispose();
  255. tabContentStyle = null;
  256. }
  257. protected void installListeners() {
  258. if ((propertyChangeListener = createPropertyChangeListener()) != null) {
  259. tabPane.addPropertyChangeListener(propertyChangeListener);
  260. }
  261. if ((tabChangeListener = createChangeListener()) != null) {
  262. tabPane.addChangeListener(tabChangeListener);
  263. }
  264. if ((mouseInputListener = createMouseInputListener()) != null) {
  265. if (scrollableTabLayoutEnabled()) {
  266. tabScroller.tabPanel.addMouseListener(mouseInputListener);
  267. tabScroller.tabPanel.addMouseMotionListener(mouseInputListener);
  268. } else { // WRAP_TAB_LAYOUT
  269. tabPane.addMouseListener(mouseInputListener);
  270. tabPane.addMouseMotionListener(mouseInputListener);
  271. }
  272. }
  273. if ((focusListener = createFocusListener()) != null) {
  274. tabPane.addFocusListener(focusListener);
  275. }
  276. // PENDING(api) : See comment for ContainerHandler
  277. if ((containerListener = new ContainerHandler()) != null) {
  278. tabPane.addContainerListener(containerListener);
  279. if (tabPane.getTabCount()>0) {
  280. htmlViews = createHTMLVector();
  281. }
  282. }
  283. }
  284. protected void uninstallListeners() {
  285. if (mouseInputListener != null) {
  286. if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
  287. tabScroller.tabPanel.removeMouseListener(mouseInputListener);
  288. tabScroller.tabPanel.removeMouseMotionListener(
  289. mouseInputListener);
  290. } else { // WRAP_TAB_LAYOUT
  291. tabPane.removeMouseListener(mouseInputListener);
  292. tabPane.removeMouseMotionListener(mouseInputListener);
  293. }
  294. mouseInputListener = null;
  295. }
  296. if (focusListener != null) {
  297. tabPane.removeFocusListener(focusListener);
  298. focusListener = null;
  299. }
  300. // PENDING(api): See comment for ContainerHandler
  301. if (containerListener != null) {
  302. tabPane.removeContainerListener(containerListener);
  303. containerListener = null;
  304. if (htmlViews!=null) {
  305. htmlViews.removeAllElements();
  306. htmlViews = null;
  307. }
  308. }
  309. if (tabChangeListener != null) {
  310. tabPane.removeChangeListener(tabChangeListener);
  311. tabChangeListener = null;
  312. }
  313. if (propertyChangeListener != null) {
  314. tabPane.removePropertyChangeListener(propertyChangeListener);
  315. propertyChangeListener = null;
  316. }
  317. }
  318. protected MouseInputListener createMouseInputListener() {
  319. return new MouseHandler();
  320. }
  321. protected FocusListener createFocusListener() {
  322. return new FocusHandler();
  323. }
  324. protected ChangeListener createChangeListener() {
  325. return new TabSelectionHandler();
  326. }
  327. protected PropertyChangeListener createPropertyChangeListener() {
  328. return new PropertyChangeHandler();
  329. }
  330. protected void installKeyboardActions() {
  331. InputMap km = getInputMap(JComponent.
  332. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  333. SwingUtilities.replaceUIInputMap(tabPane, JComponent.
  334. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  335. km);
  336. km = getInputMap(JComponent.WHEN_FOCUSED);
  337. SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
  338. LazyActionMap.installLazyActionMap(tabPane, SynthTabbedPaneUI.class,
  339. "TabbedPane.actionMap");
  340. if (scrollableTabLayoutEnabled()) {
  341. tabScroller.scrollForwardButton.setAction(
  342. scrollTabsForwardAction);
  343. tabScroller.scrollBackwardButton.setAction(
  344. scrollTabsBackwardAction);
  345. }
  346. }
  347. InputMap getInputMap(int condition) {
  348. SynthContext context = getContext(tabPane, ENABLED);
  349. InputMap map = null;
  350. if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
  351. map = (InputMap)context.getStyle().get(context,
  352. "TabbedPane.ancestorInputMap");
  353. }
  354. else if (condition == JComponent.WHEN_FOCUSED) {
  355. map = (InputMap)context.getStyle().get(context,
  356. "TabbedPane.focusInputMap");
  357. }
  358. context.dispose();
  359. return map;
  360. }
  361. protected void uninstallKeyboardActions() {
  362. SwingUtilities.replaceUIActionMap(tabPane, null);
  363. SwingUtilities.replaceUIInputMap(tabPane, JComponent.
  364. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  365. null);
  366. SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED,
  367. null);
  368. }
  369. /**
  370. * Reloads the mnemonics. This should be invoked when a memonic changes,
  371. * when the title of a mnemonic changes, or when tabs are added/removed.
  372. */
  373. private void updateMnemonics() {
  374. resetMnemonics();
  375. for (int counter = tabPane.getTabCount() - 1; counter >= 0;
  376. counter--) {
  377. int mnemonic = tabPane.getMnemonicAt(counter);
  378. if (mnemonic > 0) {
  379. addMnemonic(counter, mnemonic);
  380. }
  381. }
  382. }
  383. /**
  384. * Resets the mnemonics bindings to an empty state.
  385. */
  386. private void resetMnemonics() {
  387. if (mnemonicToIndexMap != null) {
  388. mnemonicToIndexMap.clear();
  389. mnemonicInputMap.clear();
  390. }
  391. }
  392. /**
  393. * Adds the specified mnemonic at the specified index.
  394. */
  395. private void addMnemonic(int index, int mnemonic) {
  396. if (mnemonicToIndexMap == null) {
  397. initMnemonics();
  398. }
  399. mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, Event.ALT_MASK),
  400. "setSelectedIndex");
  401. mnemonicToIndexMap.put(new Integer(mnemonic), new Integer(index));
  402. }
  403. /**
  404. * Installs the state needed for mnemonics.
  405. */
  406. private void initMnemonics() {
  407. mnemonicToIndexMap = new Hashtable();
  408. mnemonicInputMap = new InputMapUIResource();
  409. mnemonicInputMap.setParent(SwingUtilities.getUIInputMap(tabPane,
  410. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT));
  411. SwingUtilities.replaceUIInputMap(tabPane,
  412. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  413. mnemonicInputMap);
  414. }
  415. // Geometry
  416. public Dimension getPreferredSize(JComponent c) {
  417. // Default to LayoutManager's preferredLayoutSize
  418. return null;
  419. }
  420. public Dimension getMinimumSize(JComponent c) {
  421. // Default to LayoutManager's minimumLayoutSize
  422. return null;
  423. }
  424. public Dimension getMaximumSize(JComponent c) {
  425. // Default to LayoutManager's maximumLayoutSize
  426. return null;
  427. }
  428. private void repaintTab(int index) {
  429. }
  430. private void updateMouseOver(int x, int y) {
  431. setMouseOverTab(getTabAtLocation(x, y));
  432. }
  433. private void setMouseOverTab(int index) {
  434. if (index != mouseIndex) {
  435. if (mouseBounds != null) {
  436. tabPane.repaint(mouseBounds);
  437. }
  438. mouseIndex = index;
  439. if (index != -1 && index < tabPane.getTabCount()) {
  440. mouseBounds = tabPane.getBoundsAt(index);
  441. if (mouseBounds != null) {
  442. tabPane.repaint(mouseBounds);
  443. }
  444. }
  445. else {
  446. mouseBounds = null;
  447. }
  448. }
  449. }
  450. // UI Rendering
  451. public SynthContext getContext(JComponent c) {
  452. return getContext(c, getComponentState(c));
  453. }
  454. public SynthContext getContext(JComponent c, int state) {
  455. return SynthContext.getContext(SynthContext.class, c,
  456. SynthLookAndFeel.getRegion(c),style, state);
  457. }
  458. public SynthContext getContext(JComponent c, Region subregion) {
  459. return getContext(c, subregion, getComponentState(c));
  460. }
  461. private SynthContext getContext(JComponent c, Region subregion, int state){
  462. SynthStyle style = null;
  463. Class klass = SynthContext.class;
  464. if (subregion == Region.TABBED_PANE_TAB) {
  465. style = tabStyle;
  466. klass = TabContext.class;
  467. }
  468. else if (subregion == Region.TABBED_PANE_TAB_AREA) {
  469. style = tabAreaStyle;
  470. }
  471. else if (subregion == Region.TABBED_PANE_CONTENT) {
  472. style = tabContentStyle;
  473. }
  474. return SynthContext.getContext(klass, c, subregion, style, state);
  475. }
  476. private Region getRegion(JComponent c) {
  477. return SynthLookAndFeel.getRegion(c);
  478. }
  479. private int getComponentState(JComponent c) {
  480. return SynthLookAndFeel.getComponentState(c);
  481. }
  482. public void update(Graphics g, JComponent c) {
  483. SynthContext context = getContext(c);
  484. SynthLookAndFeel.update(context, g);
  485. paint(context, g);
  486. context.dispose();
  487. }
  488. public void paint(Graphics g, JComponent c) {
  489. SynthContext context = getContext(c);
  490. paint(context, g);
  491. context.dispose();
  492. }
  493. protected void paint(SynthContext context, Graphics g) {
  494. int tc = tabPane.getTabCount();
  495. if (tabCount != tc) {
  496. tabCount = tc;
  497. updateMnemonics();
  498. }
  499. int selectedIndex = tabPane.getSelectedIndex();
  500. int tabPlacement = tabPane.getTabPlacement();
  501. ensureCurrentLayout();
  502. // Paint tab area
  503. // If scrollable tabs are enabled, the tab area will be
  504. // painted by the scrollable tab panel instead.
  505. //
  506. if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
  507. Insets insets = tabPane.getInsets();
  508. int x = insets.left;
  509. int y = insets.top;
  510. int width = tabPane.getWidth() - insets.left - insets.right;
  511. int height = tabPane.getHeight() - insets.top - insets.bottom;
  512. int size;
  513. switch(tabPlacement) {
  514. case LEFT:
  515. width = calculateTabAreaWidth(tabPlacement, runCount,
  516. maxTabWidth);
  517. break;
  518. case RIGHT:
  519. size = calculateTabAreaWidth(tabPlacement, runCount,
  520. maxTabWidth);
  521. x = x + width - size;
  522. width = size;
  523. break;
  524. case BOTTOM:
  525. size = calculateTabAreaHeight(tabPlacement, runCount,
  526. maxTabHeight);
  527. y = y + height - size;
  528. height = size;
  529. break;
  530. case TOP:
  531. default:
  532. height = calculateTabAreaHeight(tabPlacement, runCount,
  533. maxTabHeight);
  534. }
  535. paintTabArea(tabAreaContext, g, tabPlacement,
  536. selectedIndex, new Rectangle(x, y, width, height));
  537. }
  538. // Paint content border
  539. paintContentBorder(tabContentContext, g, tabPlacement, selectedIndex);
  540. }
  541. /**
  542. * Paints the tabs in the tab area.
  543. * Invoked by paint().
  544. * The graphics parameter must be a valid <code>Graphics</code>
  545. * object. Tab placement may be either:
  546. * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
  547. * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
  548. * The selected index must be a valid tabbed pane tab index (0 to
  549. * tab count - 1, inclusive) or -1 if no tab is currently selected.
  550. * The handling of invalid parameters is unspecified.
  551. *
  552. * @param g the graphics object to use for rendering
  553. * @param tabPlacement the placement for the tabs within the JTabbedPane
  554. * @param selectedIndex the tab index of the selected component
  555. *
  556. * @since 1.4
  557. */
  558. protected void paintTabArea(SynthContext ss, Graphics g,
  559. int tabPlacement, int selectedIndex,
  560. Rectangle tabAreaBounds) {
  561. // Paint the tab area.
  562. SynthLookAndFeel.updateSubregion(ss, g, tabAreaBounds);
  563. int tabCount = tabPane.getTabCount();
  564. Rectangle iconRect = new Rectangle(),
  565. textRect = new Rectangle();
  566. Rectangle clipRect = g.getClipBounds();
  567. // Paint tabRuns of tabs from back to front
  568. for (int i = runCount - 1; i >= 0; i--) {
  569. int start = tabRuns[i];
  570. int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
  571. int end = (next != 0? next - 1: tabCount - 1);
  572. for (int j = start; j <= end; j++) {
  573. if (rects[j].intersects(clipRect) && selectedIndex != j) {
  574. paintTab(tabContext, g, tabPlacement, rects, j, iconRect,
  575. textRect);
  576. }
  577. }
  578. }
  579. if (selectedIndex >= 0) {
  580. if (rects[selectedIndex].intersects(clipRect)) {
  581. paintTab(tabContext, g, tabPlacement, rects, selectedIndex,
  582. iconRect, textRect);
  583. }
  584. }
  585. }
  586. protected void paintTab(SynthContext ss, Graphics g,
  587. int tabPlacement, Rectangle[] rects, int tabIndex,
  588. Rectangle iconRect, Rectangle textRect) {
  589. Rectangle tabRect = rects[tabIndex];
  590. int selectedIndex = tabPane.getSelectedIndex();
  591. boolean isSelected = selectedIndex == tabIndex;
  592. tabContext.update(tabIndex, isSelected, (mouseIndex == tabIndex),
  593. (focusIndex == tabIndex));
  594. SynthLookAndFeel.updateSubregion(ss, g, tabRect);
  595. String title = tabPane.getTitleAt(tabIndex);
  596. Font font = ss.getStyle().getFont(ss);
  597. FontMetrics metrics = g.getFontMetrics(font);
  598. Icon icon = getIconForTab(tabIndex);
  599. layoutLabel(ss, tabPlacement, metrics, tabIndex, title, icon,
  600. tabRect, iconRect, textRect, isSelected);
  601. paintText(ss, g, tabPlacement, font, metrics,
  602. tabIndex, title, textRect, isSelected);
  603. paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
  604. }
  605. protected void layoutLabel(SynthContext ss, int tabPlacement,
  606. FontMetrics metrics, int tabIndex,
  607. String title, Icon icon,
  608. Rectangle tabRect, Rectangle iconRect,
  609. Rectangle textRect, boolean isSelected ) {
  610. View v = getTextViewForTab(tabIndex);
  611. if (v != null) {
  612. tabPane.putClientProperty("html", v);
  613. }
  614. textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
  615. ss.getStyle().getSynthGraphics(ss).layoutText(ss, metrics, title,
  616. icon, SwingUtilities.CENTER, SwingUtilities.CENTER,
  617. SwingUtilities.LEADING, SwingUtilities.TRAILING,
  618. tabRect, iconRect, textRect, getTextIconGap());
  619. tabPane.putClientProperty("html", null);
  620. // PENDING: Can these be nuked? Don't fit well...
  621. // If this is necessary, they should be in a special TextLayout
  622. // that special cases this behavior.
  623. int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
  624. int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
  625. iconRect.x += xNudge;
  626. iconRect.y += yNudge;
  627. textRect.x += xNudge;
  628. textRect.y += yNudge;
  629. }
  630. protected void paintIcon(Graphics g, int tabPlacement,
  631. int tabIndex, Icon icon, Rectangle iconRect,
  632. boolean isSelected ) {
  633. if (icon != null) {
  634. icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
  635. }
  636. }
  637. protected void paintText(SynthContext ss,
  638. Graphics g, int tabPlacement,
  639. Font font, FontMetrics metrics, int tabIndex,
  640. String title, Rectangle textRect,
  641. boolean isSelected) {
  642. g.setFont(font);
  643. View v = getTextViewForTab(tabIndex);
  644. if (v != null) {
  645. // html
  646. v.paint(g, textRect);
  647. } else {
  648. // plain text
  649. int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
  650. g.setColor(ss.getStyle().getColor(ss, ColorType.TEXT_FOREGROUND));
  651. ss.getStyle().getSynthGraphics(ss).paintText(ss, g, title,
  652. textRect, mnemIndex);
  653. }
  654. }
  655. protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
  656. Rectangle tabRect = rects[tabIndex];
  657. int nudge = 0;
  658. switch(tabPlacement) {
  659. case LEFT:
  660. nudge = isSelected? -1 : 1;
  661. break;
  662. case RIGHT:
  663. nudge = isSelected? 1 : -1;
  664. break;
  665. case BOTTOM:
  666. case TOP:
  667. default:
  668. nudge = tabRect.width % 2;
  669. }
  670. return nudge;
  671. }
  672. protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
  673. Rectangle tabRect = rects[tabIndex];
  674. int nudge = 0;
  675. switch(tabPlacement) {
  676. case BOTTOM:
  677. nudge = isSelected? 1 : -1;
  678. break;
  679. case LEFT:
  680. case RIGHT:
  681. nudge = tabRect.height % 2;
  682. break;
  683. case TOP:
  684. default:
  685. nudge = isSelected? -1 : 1;;
  686. }
  687. return nudge;
  688. }
  689. protected void paintContentBorder(SynthContext ss, Graphics g,
  690. int tabPlacement, int selectedIndex) {
  691. int width = tabPane.getWidth();
  692. int height = tabPane.getHeight();
  693. Insets insets = tabPane.getInsets();
  694. int x = insets.left;
  695. int y = insets.top;
  696. int w = width - insets.right - insets.left;
  697. int h = height - insets.top - insets.bottom;
  698. switch(tabPlacement) {
  699. case LEFT:
  700. x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  701. w -= (x - insets.left);
  702. break;
  703. case RIGHT:
  704. w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  705. break;
  706. case BOTTOM:
  707. h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  708. break;
  709. case TOP:
  710. default:
  711. y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  712. h -= (y - insets.top);
  713. }
  714. SynthLookAndFeel.updateSubregion(ss, g, new Rectangle(x, y, w, h));
  715. }
  716. private void ensureCurrentLayout() {
  717. if (!tabPane.isValid()) {
  718. tabPane.validate();
  719. }
  720. /* If tabPane doesn't have a peer yet, the validate() call will
  721. * silently fail. We handle that by forcing a layout if tabPane
  722. * is still invalid. See bug 4237677.
  723. */
  724. if (!tabPane.isValid()) {
  725. TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
  726. layout.calculateLayoutInfo();
  727. }
  728. }
  729. // TabbedPaneUI methods
  730. /**
  731. * Returns the bounds of the specified tab index. The bounds are
  732. * with respect to the JTabbedPane's coordinate space.
  733. */
  734. public Rectangle getTabBounds(JTabbedPane pane, int i) {
  735. ensureCurrentLayout();
  736. Rectangle tabRect = new Rectangle();
  737. return getTabBounds(i, tabRect);
  738. }
  739. public int getTabRunCount(JTabbedPane pane) {
  740. ensureCurrentLayout();
  741. return runCount;
  742. }
  743. /**
  744. * Returns the tab index which intersects the specified point
  745. * in the JTabbedPane's coordinate space.
  746. */
  747. public int tabForCoordinate(JTabbedPane pane, int x, int y) {
  748. ensureCurrentLayout();
  749. Point p = new Point(x, y);
  750. if (scrollableTabLayoutEnabled()) {
  751. translatePointToTabPanel(x, y, p);
  752. }
  753. int tabCount = tabPane.getTabCount();
  754. for (int i = 0; i < tabCount; i++) {
  755. if (rects[i].contains(p.x, p.y)) {
  756. return i;
  757. }
  758. }
  759. return -1;
  760. }
  761. /**
  762. * Returns the bounds of the specified tab in the coordinate space
  763. * of the JTabbedPane component. This is required because the tab rects
  764. * are by default defined in the coordinate space of the component where
  765. * they are rendered, which could be the JTabbedPane
  766. * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
  767. * This method should be used whenever the tab rectangle must be relative
  768. * to the JTabbedPane itself and the result should be placed in a
  769. * designated Rectangle object (rather than instantiating and returning
  770. * a new Rectangle each time). The tab index parameter must be a valid
  771. * tabbed pane tab index (0 to tab count - 1, inclusive). The destination
  772. * rectangle parameter must be a valid <code>Rectangle</code> instance.
  773. * The handling of invalid parameters is unspecified.
  774. *
  775. * @param tabIndex the index of the tab
  776. * @param dest the rectangle where the result should be placed
  777. * @return the resulting rectangle
  778. *
  779. * @since 1.4
  780. */
  781. protected Rectangle getTabBounds(int tabIndex, Rectangle dest) {
  782. dest.width = rects[tabIndex].width;
  783. dest.height = rects[tabIndex].height;
  784. if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
  785. // Need to translate coordinates based on viewport location &
  786. // view position
  787. Point vpp = tabScroller.viewport.getLocation();
  788. Point viewp = tabScroller.viewport.getViewPosition();
  789. dest.x = rects[tabIndex].x-vpp.x-viewp.x;
  790. dest.y = rects[tabIndex].y-vpp.y-viewp.y;
  791. } else { // WRAP_TAB_LAYOUT
  792. dest.x = rects[tabIndex].x;
  793. dest.y = rects[tabIndex].y;
  794. }
  795. return dest;
  796. }
  797. /**
  798. * Returns the tab index which intersects the specified point
  799. * in the coordinate space of the component where the
  800. * tabs are actually rendered, which could be the JTabbedPane
  801. * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
  802. */
  803. private int getTabAtLocation(int x, int y) {
  804. ensureCurrentLayout();
  805. int tabCount = tabPane.getTabCount();
  806. for (int i = 0; i < tabCount; i++) {
  807. if (rects[i].contains(x, y)) {
  808. return i;
  809. }
  810. }
  811. return -1;
  812. }
  813. /**
  814. * Returns the index of the tab closest to the passed in location, note
  815. * that the returned tab may not contain the location x,y.
  816. */
  817. private int getClosestTab(int x, int y) {
  818. int min = 0;
  819. int tabCount = Math.min(rects.length, tabPane.getTabCount());
  820. int max = tabCount;
  821. int tabPlacement = tabPane.getTabPlacement();
  822. boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM);
  823. int want = (useX) ? x : y;
  824. while (min != max) {
  825. int current = (max + min) / 2;
  826. int minLoc;
  827. int maxLoc;
  828. if (useX) {
  829. minLoc = rects[current].x;
  830. maxLoc = minLoc + rects[current].width;
  831. }
  832. else {
  833. minLoc = rects[current].y;
  834. maxLoc = minLoc + rects[current].height;
  835. }
  836. if (want < minLoc) {
  837. max = current;
  838. if (min == max) {
  839. return Math.max(0, current - 1);
  840. }
  841. }
  842. else if (want >= maxLoc) {
  843. min = current;
  844. if (max - min <= 1) {
  845. return Math.max(current + 1, tabCount - 1);
  846. }
  847. }
  848. else {
  849. return current;
  850. }
  851. }
  852. return min;
  853. }
  854. /**
  855. * Returns a point which is translated from the specified point in the
  856. * JTabbedPane's coordinate space to the coordinate space of the
  857. * ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY.
  858. */
  859. private Point translatePointToTabPanel(int srcx, int srcy, Point dest) {
  860. Point vpp = tabScroller.viewport.getLocation();
  861. Point viewp = tabScroller.viewport.getViewPosition();
  862. dest.x = srcx + vpp.x + viewp.x;
  863. dest.y = srcy + vpp.y + viewp.y;
  864. return dest;
  865. }
  866. // BasicTabbedPaneUI methods
  867. protected Component getVisibleComponent() {
  868. return visibleComponent;
  869. }
  870. protected void setVisibleComponent(Component component) {
  871. if (visibleComponent != null && visibleComponent != component &&
  872. visibleComponent.getParent() == tabPane) {
  873. visibleComponent.setVisible(false);
  874. }
  875. if (component != null && !component.isVisible()) {
  876. component.setVisible(true);
  877. }
  878. visibleComponent = component;
  879. }
  880. protected void assureRectsCreated(int tabCount) {
  881. int rectArrayLen = rects.length;
  882. if (tabCount != rectArrayLen ) {
  883. Rectangle[] tempRectArray = new Rectangle[tabCount];
  884. System.arraycopy(rects, 0, tempRectArray, 0,
  885. Math.min(rectArrayLen, tabCount));
  886. rects = tempRectArray;
  887. for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
  888. rects[rectIndex] = new Rectangle();
  889. }
  890. }
  891. }
  892. protected void expandTabRunsArray() {
  893. int rectLen = tabRuns.length;
  894. int[] newArray = new int[rectLen+10];
  895. System.arraycopy(tabRuns, 0, newArray, 0, runCount);
  896. tabRuns = newArray;
  897. }
  898. protected int getRunForTab(int tabCount, int tabIndex) {
  899. for (int i = 0; i < runCount; i++) {
  900. int first = tabRuns[i];
  901. int last = lastTabInRun(tabCount, i);
  902. if (tabIndex >= first && tabIndex <= last) {
  903. return i;
  904. }
  905. }
  906. return 0;
  907. }
  908. protected int lastTabInRun(int tabCount, int run) {
  909. if (runCount == 1) {
  910. return tabCount - 1;
  911. }
  912. int nextRun = (run == runCount - 1? 0 : run + 1);
  913. if (tabRuns[nextRun] == 0) {
  914. return tabCount - 1;
  915. }
  916. return tabRuns[nextRun]-1;
  917. }
  918. protected int getTabRunOverlay(int tabPlacement) {
  919. return tabRunOverlay;
  920. }
  921. protected int getTabRunIndent(int tabPlacement, int run) {
  922. return 0;
  923. }
  924. protected boolean shouldPadTabRun(int tabPlacement, int run) {
  925. return runCount > 1;
  926. }
  927. protected boolean shouldRotateTabRuns(int tabPlacement) {
  928. return true;
  929. }
  930. protected Icon getIconForTab(int tabIndex) {
  931. return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex))?
  932. tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex);
  933. }
  934. /**
  935. * Returns the text View object required to render stylized text (HTML) for
  936. * the specified tab or null if no specialized text rendering is needed
  937. * for this tab. This is provided to support html rendering inside tabs.
  938. *
  939. * @param tabIndex the index of the tab
  940. * @return the text view to render the tab's text or null if no
  941. * specialized rendering is required
  942. *
  943. * @since 1.4
  944. */
  945. protected View getTextViewForTab(int tabIndex) {
  946. if (htmlViews != null) {
  947. return (View)htmlViews.elementAt(tabIndex);
  948. }
  949. return null;
  950. }
  951. protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
  952. int height = 0;
  953. View v = getTextViewForTab(tabIndex);
  954. if (v != null) {
  955. // html
  956. height += (int)v.getPreferredSpan(View.Y_AXIS);
  957. } else {
  958. // plain text
  959. height += fontHeight;
  960. }
  961. Icon icon = getIconForTab(tabIndex);
  962. Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
  963. if (icon != null) {
  964. height = Math.max(height, icon.getIconHeight());
  965. }
  966. height += tabInsets.top + tabInsets.bottom + 2;
  967. return height;
  968. }
  969. protected int calculateMaxTabHeight(int tabPlacement) {
  970. FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont(
  971. tabContext));
  972. int tabCount = tabPane.getTabCount();
  973. int result = 0;
  974. int fontHeight = metrics.getHeight();
  975. for(int i = 0; i < tabCount; i++) {
  976. result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
  977. }
  978. return result;
  979. }
  980. protected int calculateTabWidth(int tabPlacement, int tabIndex,
  981. FontMetrics metrics) {
  982. Icon icon = getIconForTab(tabIndex);
  983. Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
  984. int width = tabInsets.left + tabInsets.right + 3;
  985. if (icon != null) {
  986. // PENDING: This should call to SynthIcon for size.
  987. width += icon.getIconWidth() + getTextIconGap();
  988. }
  989. View v = getTextViewForTab(tabIndex);
  990. if (v != null) {
  991. // html
  992. width += (int)v.getPreferredSpan(View.X_AXIS);
  993. } else {
  994. // plain text
  995. String title = tabPane.getTitleAt(tabIndex);
  996. width += tabContext.getStyle().getSynthGraphics(tabContext).
  997. computeStringWidth(tabContext, metrics.getFont(),
  998. metrics, title);
  999. }
  1000. return width;
  1001. }
  1002. private int getTextIconGap() {
  1003. Integer gap = (Integer)tabContext.getStyle().get(tabContext,"textIconGap");
  1004. if (gap == null) {
  1005. return 0;
  1006. }
  1007. return gap.intValue();
  1008. }
  1009. protected int calculateMaxTabWidth(int tabPlacement) {
  1010. FontMetrics metrics = getFontMetrics(tabContext.getStyle().getFont(
  1011. tabContext));
  1012. int tabCount = tabPane.getTabCount();
  1013. int result = 0;
  1014. for(int i = 0; i < tabCount; i++) {
  1015. result = Math.max(calculateTabWidth(tabPlacement, i, metrics),
  1016. result);
  1017. }
  1018. return result;
  1019. }
  1020. protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount,
  1021. int maxTabHeight) {
  1022. Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
  1023. int tabRunOverlay = getTabRunOverlay(tabPlacement);
  1024. return (horizRunCount > 0?
  1025. horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay +
  1026. tabAreaInsets.top + tabAreaInsets.bottom :
  1027. 0);
  1028. }
  1029. protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount,
  1030. int maxTabWidth) {
  1031. Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
  1032. int tabRunOverlay = getTabRunOverlay(tabPlacement);
  1033. return (vertRunCount > 0?
  1034. vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay +
  1035. tabAreaInsets.left + tabAreaInsets.right :
  1036. 0);
  1037. }
  1038. protected Insets getTabInsets(int tabPlacement, int tabIndex) {
  1039. tabContext.update(tabIndex, false, (mouseIndex == tabIndex),
  1040. (focusIndex == tabIndex));
  1041. return tabContext.getStyle().getInsets(tabContext, null);
  1042. }
  1043. protected Insets getSelectedTabPadInsets(int tabPlacement) {
  1044. Insets insets = (Insets)tabContext.getStyle().get(tabContext,
  1045. "selectedTabPadInsets");
  1046. if (insets == null) {
  1047. return new Insets(0, 0, 0, 0);
  1048. }
  1049. Insets result = new Insets(0, 0, 0, 0);
  1050. rotateInsets(insets, new Insets(0, 0, 0, 0), tabPlacement);
  1051. return result;
  1052. }
  1053. protected Insets getTabAreaInsets(int tabPlacement) {
  1054. return tabAreaContext.getStyle().getInsets(tabAreaContext, null);
  1055. }
  1056. protected Insets getContentBorderInsets(int tabPlacement) {
  1057. return tabContentContext.getStyle().getInsets(tabContentContext,
  1058. null);
  1059. }
  1060. protected FontMetrics getFontMetrics() {
  1061. return getFontMetrics(tabContext.getStyle().getFont(tabContext));
  1062. }
  1063. protected FontMetrics getFontMetrics(Font font) {
  1064. return Toolkit.getDefaultToolkit().getFontMetrics(font);
  1065. }
  1066. // Tab Navigation methods
  1067. protected void navigateSelectedTab(int direction) {
  1068. int tabPlacement = tabPane.getTabPlacement();
  1069. int current = selectionFollowsFocus? tabPane.getSelectedIndex() :
  1070. focusIndex;
  1071. int tabCount = tabPane.getTabCount();
  1072. // If we have no tabs then don't navigate.
  1073. if (tabCount <= 0) {
  1074. return;
  1075. }
  1076. int offset;
  1077. switch(tabPlacement) {
  1078. case NEXT:
  1079. selectNextTab(current);
  1080. break;
  1081. case PREVIOUS:
  1082. selectPreviousTab(current);
  1083. break;
  1084. case LEFT:
  1085. case RIGHT:
  1086. switch(direction) {
  1087. case NORTH:
  1088. selectPreviousTabInRun(current);
  1089. break;
  1090. case SOUTH:
  1091. selectNextTabInRun(current);
  1092. break;
  1093. case WEST:
  1094. offset = getTabRunOffset(tabPlacement, tabCount, current, false);
  1095. selectAdjacentRunTab(tabPlacement, current, offset);
  1096. break;
  1097. case EAST:
  1098. offset = getTabRunOffset(tabPlacement, tabCount, current, true);
  1099. selectAdjacentRunTab(tabPlacement, current, offset);
  1100. break;
  1101. default:
  1102. }
  1103. break;
  1104. case BOTTOM:
  1105. case TOP:
  1106. default:
  1107. switch(direction) {
  1108. case NORTH:
  1109. offset = getTabRunOffset(tabPlacement, tabCount, current, false);
  1110. selectAdjacentRunTab(tabPlacement, current, offset);
  1111. break;
  1112. case SOUTH:
  1113. offset = getTabRunOffset(tabPlacement, tabCount, current, true);
  1114. selectAdjacentRunTab(tabPlacement, current, offset);
  1115. break;
  1116. case EAST:
  1117. selectNextTabInRun(current);
  1118. break;
  1119. case WEST:
  1120. selectPreviousTabInRun(current);
  1121. break;
  1122. default:
  1123. }
  1124. }
  1125. }
  1126. protected void selectNextTabInRun(int current) {
  1127. int tabCount = tabPane.getTabCount();
  1128. int tabIndex = getNextTabIndexInRun(tabCount, current);
  1129. while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
  1130. tabIndex = getNextTabIndexInRun(tabCount, tabIndex);
  1131. }
  1132. navigateTo(tabIndex);
  1133. }
  1134. protected void selectPreviousTabInRun(int current) {
  1135. int tabCount = tabPane.getTabCount();
  1136. int tabIndex = getPreviousTabIndexInRun(tabCount, current);
  1137. while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
  1138. tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex);
  1139. }
  1140. navigateTo(tabIndex);
  1141. }
  1142. protected void selectNextTab(int current) {
  1143. int tabIndex = getNextTabIndex(current);
  1144. while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
  1145. tabIndex = getNextTabIndex(tabIndex);
  1146. }
  1147. navigateTo(tabIndex);
  1148. }
  1149. protected void selectPreviousTab(int current) {
  1150. int tabIndex = getPreviousTabIndex(current);
  1151. while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
  1152. tabIndex = getPreviousTabIndex(tabIndex);
  1153. }
  1154. navigateTo(tabIndex);
  1155. }
  1156. protected void selectAdjacentRunTab(int tabPlacement,
  1157. int tabIndex, int offset) {
  1158. if ( runCount < 2 ) {
  1159. return;
  1160. }
  1161. int newIndex;
  1162. Rectangle r = rects[tabIndex];
  1163. switch(tabPlacement) {
  1164. case LEFT:
  1165. case RIGHT:
  1166. newIndex = getTabAtLocation(r.x + r.width2 + offset,
  1167. r.y + r.height2);
  1168. break;
  1169. case BOTTOM:
  1170. case TOP:
  1171. default:
  1172. newIndex = getTabAtLocation(r.x + r.width2,
  1173. r.y + r.height2 + offset);
  1174. }
  1175. if (newIndex != -1) {
  1176. while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
  1177. newIndex = getNextTabIndex(newIndex);
  1178. }
  1179. navigateTo(newIndex);
  1180. }
  1181. }
  1182. private void navigateTo(int index) {
  1183. if (selectionFollowsFocus) {
  1184. setFocusIndex(index);
  1185. tabPane.setSelectedIndex(index);
  1186. } else {
  1187. // Just move focus (not selection)
  1188. int oldFocusIndex = focusIndex;
  1189. setFocusIndex(index);
  1190. tabPane.repaint(getTabBounds(tabPane, focusIndex));
  1191. if (oldFocusIndex != -1) {
  1192. tabPane.repaint(getTabBounds(tabPane, oldFocusIndex));
  1193. }
  1194. }
  1195. }
  1196. void setFocusIndex(int index) {
  1197. focusIndex = index;
  1198. }
  1199. int getFocusIndex() {
  1200. return focusIndex;
  1201. }
  1202. protected int getTabRunOffset(int tabPlacement, int tabCount,
  1203. int tabIndex, boolean forward) {
  1204. int run = getRunForTab(tabCount, tabIndex);
  1205. int offset;
  1206. switch(tabPlacement) {
  1207. case LEFT: {
  1208. if (run == 0) {
  1209. offset = (forward?
  1210. -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
  1211. -maxTabWidth);
  1212. } else if (run == runCount - 1) {
  1213. offset = (forward?
  1214. maxTabWidth :
  1215. calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
  1216. } else {
  1217. offset = (forward? maxTabWidth : -maxTabWidth);
  1218. }
  1219. break;
  1220. }
  1221. case RIGHT: {
  1222. if (run == 0) {
  1223. offset = (forward?
  1224. maxTabWidth :
  1225. calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
  1226. } else if (run == runCount - 1) {
  1227. offset = (forward?
  1228. -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
  1229. -maxTabWidth);
  1230. } else {
  1231. offset = (forward? maxTabWidth : -maxTabWidth);
  1232. }
  1233. break;
  1234. }
  1235. case BOTTOM: {
  1236. if (run == 0) {
  1237. offset = (forward?
  1238. maxTabHeight :
  1239. calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
  1240. } else if (run == runCount - 1) {
  1241. offset = (forward?
  1242. -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
  1243. -maxTabHeight);
  1244. } else {
  1245. offset = (forward? maxTabHeight : -maxTabHeight);
  1246. }
  1247. break;
  1248. }
  1249. case TOP:
  1250. default: {
  1251. if (run == 0) {
  1252. offset = (forward?
  1253. -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
  1254. -maxTabHeight);
  1255. } else if (run == runCount - 1) {
  1256. offset = (forward?
  1257. maxTabHeight :
  1258. calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
  1259. } else {
  1260. offset = (forward? maxTabHeight : -maxTabHeight);
  1261. }
  1262. }
  1263. }
  1264. return offset;
  1265. }
  1266. protected int getPreviousTabIndex(int base) {
  1267. int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1);
  1268. return (tabIndex >= 0? tabIndex : 0);
  1269. }
  1270. protected int getNextTabIndex(int base) {
  1271. return (base+1)%tabPane.getTabCount();
  1272. }
  1273. protected int getNextTabIndexInRun(int tabCount, int base) {
  1274. if (runCount < 2) {
  1275. return getNextTabIndex(base);
  1276. }
  1277. int currentRun = getRunForTab(tabCount, base);
  1278. int next = getNextTabIndex(base);
  1279. if (next == tabRuns[getNextTabRun(currentRun)]) {
  1280. return tabRuns[currentRun];
  1281. }
  1282. return next;
  1283. }
  1284. protected int getPreviousTabIndexInRun(int tabCount, int base) {
  1285. if (runCount < 2) {
  1286. return getPreviousTabIndex(base);
  1287. }
  1288. int currentRun = getRunForTab(tabCount, base);
  1289. if (base == tabRuns[currentRun]) {
  1290. int previous = tabRuns[getNextTabRun(currentRun)]-1;
  1291. return (previous != -1? previous : tabCount-1);
  1292. }
  1293. return getPreviousTabIndex(base);
  1294. }
  1295. protected int getPreviousTabRun(int baseRun) {
  1296. int runIndex = (baseRun - 1 >= 0? baseRun - 1 : runCount - 1);
  1297. return (runIndex >= 0? runIndex : 0);
  1298. }
  1299. protected int getNextTabRun(int baseRun) {
  1300. return (baseRun+1)%runCount;
  1301. }
  1302. protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) {
  1303. switch(targetPlacement) {
  1304. case LEFT:
  1305. targetInsets.top = topInsets.left;
  1306. targetInsets.left = topInsets.top;
  1307. targetInsets.bottom = topInsets.right;
  1308. targetInsets.right = topInsets.bottom;
  1309. break;
  1310. case BOTTOM:
  1311. targetInsets.top = topInsets.bottom;
  1312. targetInsets.left = topInsets.left;
  1313. targetInsets.bottom = topInsets.top;
  1314. targetInsets.right = topInsets.right;
  1315. break;
  1316. case RIGHT:
  1317. targetInsets.top = topInsets.left;
  1318. targetInsets.left = topInsets.bottom;
  1319. targetInsets.bottom = topInsets.right;
  1320. targetInsets.right = topInsets.top;
  1321. break;
  1322. case TOP:
  1323. default:
  1324. targetInsets.top = topInsets.top;
  1325. targetInsets.left = topInsets.left;
  1326. targetInsets.bottom = topInsets.bottom;
  1327. targetInsets.right = topInsets.right;
  1328. }
  1329. }
  1330. // REMIND(aim,7/29/98): This method should be made
  1331. // protected in the next release where
  1332. // API changes are allowed
  1333. //
  1334. boolean requestFocusForVisibleComponent() {
  1335. Component visibleComponent = getVisibleComponent();
  1336. if (visibleComponent.isFocusTraversable()) {
  1337. visibleComponent.requestFocus();
  1338. return true;
  1339. } else if (visibleComponent instanceof JComponent) {
  1340. if (((JComponent)visibleComponent).requestDefaultFocus()) {
  1341. return true;
  1342. }
  1343. }
  1344. return false;
  1345. }
  1346. private static class RightAction extends AbstractAction {
  1347. public void actionPerformed(ActionEvent e) {
  1348. JTabbedPane pane = (JTabbedPane)e.getSource();
  1349. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1350. ui.navigateSelectedTab(EAST);
  1351. }
  1352. };
  1353. private static class LeftAction extends AbstractAction {
  1354. public void actionPerformed(ActionEvent e) {
  1355. JTabbedPane pane = (JTabbedPane)e.getSource();
  1356. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1357. ui.navigateSelectedTab(WEST);
  1358. }
  1359. };
  1360. private static class UpAction extends AbstractAction {
  1361. public void actionPerformed(ActionEvent e) {
  1362. JTabbedPane pane = (JTabbedPane)e.getSource();
  1363. SynthTabbedPaneUI ui = (