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 = (SynthTabbedPaneUI)pane.getUI();
  1364. ui.navigateSelectedTab(NORTH);
  1365. }
  1366. };
  1367. private static class DownAction extends AbstractAction {
  1368. public void actionPerformed(ActionEvent e) {
  1369. JTabbedPane pane = (JTabbedPane)e.getSource();
  1370. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1371. ui.navigateSelectedTab(SOUTH);
  1372. }
  1373. };
  1374. private static class NextAction extends AbstractAction {
  1375. public void actionPerformed(ActionEvent e) {
  1376. JTabbedPane pane = (JTabbedPane)e.getSource();
  1377. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1378. ui.navigateSelectedTab(NEXT);
  1379. }
  1380. };
  1381. private static class PreviousAction extends AbstractAction {
  1382. public void actionPerformed(ActionEvent e) {
  1383. JTabbedPane pane = (JTabbedPane)e.getSource();
  1384. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1385. ui.navigateSelectedTab(PREVIOUS);
  1386. }
  1387. };
  1388. private static class PageUpAction extends AbstractAction {
  1389. public void actionPerformed(ActionEvent e) {
  1390. JTabbedPane pane = (JTabbedPane)e.getSource();
  1391. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1392. int tabPlacement = pane.getTabPlacement();
  1393. if (tabPlacement == TOP|| tabPlacement == BOTTOM) {
  1394. ui.navigateSelectedTab(WEST);
  1395. } else {
  1396. ui.navigateSelectedTab(NORTH);
  1397. }
  1398. }
  1399. };
  1400. private static class PageDownAction extends AbstractAction {
  1401. public void actionPerformed(ActionEvent e) {
  1402. JTabbedPane pane = (JTabbedPane)e.getSource();
  1403. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1404. int tabPlacement = pane.getTabPlacement();
  1405. if (tabPlacement == TOP || tabPlacement == BOTTOM) {
  1406. ui.navigateSelectedTab(EAST);
  1407. } else {
  1408. ui.navigateSelectedTab(SOUTH);
  1409. }
  1410. }
  1411. };
  1412. private static class RequestFocusAction extends AbstractAction {
  1413. public void actionPerformed(ActionEvent e) {
  1414. JTabbedPane pane = (JTabbedPane)e.getSource();
  1415. pane.requestFocus();
  1416. }
  1417. };
  1418. private static class RequestFocusForVisibleAction extends AbstractAction {
  1419. public void actionPerformed(ActionEvent e) {
  1420. JTabbedPane pane = (JTabbedPane)e.getSource();
  1421. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1422. ui.requestFocusForVisibleComponent();
  1423. }
  1424. };
  1425. /**
  1426. * Selects a tab in the JTabbedPane based on the String of the
  1427. * action command. The tab selected is based on the first tab that
  1428. * has a mnemonic matching the first character of the action command.
  1429. */
  1430. private static class SetSelectedIndexAction extends AbstractAction {
  1431. public void actionPerformed(ActionEvent e) {
  1432. JTabbedPane pane = (JTabbedPane)e.getSource();
  1433. if (pane != null && (pane.getUI() instanceof SynthTabbedPaneUI)) {
  1434. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1435. String command = e.getActionCommand();
  1436. if (command != null && command.length() > 0) {
  1437. int mnemonic = (int)e.getActionCommand().charAt(0);
  1438. if (mnemonic >= 'a' && mnemonic <='z') {
  1439. mnemonic -= ('a' - 'A');
  1440. }
  1441. Integer index = (Integer)ui.mnemonicToIndexMap.
  1442. get(new Integer(mnemonic));
  1443. if (index != null && pane.isEnabledAt(index.intValue())) {
  1444. pane.setSelectedIndex(index.intValue());
  1445. }
  1446. }
  1447. }
  1448. }
  1449. };
  1450. private static class SelectFocusIndexAction extends AbstractAction {
  1451. public void actionPerformed(ActionEvent e) {
  1452. JTabbedPane pane = (JTabbedPane)e.getSource();
  1453. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1454. int focusIndex = ui.getFocusIndex();
  1455. if (focusIndex != -1) {
  1456. pane.setSelectedIndex(focusIndex);
  1457. }
  1458. }
  1459. };
  1460. private static class ScrollTabsForwardAction extends AbstractAction {
  1461. public void actionPerformed(ActionEvent e) {
  1462. JTabbedPane pane = null;
  1463. Object src = e.getSource();
  1464. if (src instanceof JTabbedPane) {
  1465. pane = (JTabbedPane)src;
  1466. } else if (src instanceof ScrollableTabButton) {
  1467. pane = (JTabbedPane)((ScrollableTabButton)src).getParent();
  1468. } else {
  1469. return; // shouldn't happen
  1470. }
  1471. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1472. if (ui.scrollableTabLayoutEnabled()) {
  1473. ui.tabScroller.scrollForward(pane.getTabPlacement());
  1474. }
  1475. }
  1476. }
  1477. private static class ScrollTabsBackwardAction extends AbstractAction {
  1478. public void actionPerformed(ActionEvent e) {
  1479. JTabbedPane pane = null;
  1480. Object src = e.getSource();
  1481. if (src instanceof JTabbedPane) {
  1482. pane = (JTabbedPane)src;
  1483. } else if (src instanceof ScrollableTabButton) {
  1484. pane = (JTabbedPane)((ScrollableTabButton)src).getParent();
  1485. } else {
  1486. return; // shouldn't happen
  1487. }
  1488. SynthTabbedPaneUI ui = (SynthTabbedPaneUI)pane.getUI();
  1489. if (ui.scrollableTabLayoutEnabled()) {
  1490. ui.tabScroller.scrollBackward(pane.getTabPlacement());
  1491. }
  1492. }
  1493. }
  1494. /**
  1495. * This inner class is marked "public" due to a compiler bug.
  1496. * This class should be treated as a "protected" inner class.
  1497. * Instantiate it only within subclasses of SynthTabbedPaneUI.
  1498. */
  1499. class TabbedPaneLayout implements LayoutManager {
  1500. public void addLayoutComponent(String name, Component comp) {}
  1501. public void removeLayoutComponent(Component comp) {}
  1502. public Dimension preferredLayoutSize(Container parent) {
  1503. return calculateSize(false);
  1504. }
  1505. public Dimension minimumLayoutSize(Container parent) {
  1506. return calculateSize(true);
  1507. }
  1508. protected Dimension calculateSize(boolean minimum) {
  1509. int tabPlacement = tabPane.getTabPlacement();
  1510. Insets insets = tabPane.getInsets();
  1511. Insets contentInsets = getContentBorderInsets(tabPlacement);
  1512. Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
  1513. Dimension zeroSize = new Dimension(0,0);
  1514. int height = contentInsets.top + contentInsets.bottom;
  1515. int width = contentInsets.left + contentInsets.right;
  1516. int cWidth = 0;
  1517. int cHeight = 0;
  1518. // Determine minimum size required to display largest
  1519. // child in each dimension
  1520. //
  1521. for (int i = 0; i < tabPane.getTabCount(); i++) {
  1522. Component component = tabPane.getComponentAt(i);
  1523. if (component != null) {
  1524. Dimension size = zeroSize;
  1525. size = minimum? component.getMinimumSize() :
  1526. component.getPreferredSize();
  1527. if (size != null) {
  1528. cHeight = Math.max(size.height, cHeight);
  1529. cWidth = Math.max(size.width, cWidth);
  1530. }
  1531. }
  1532. }
  1533. // Add content border insets to minimum size
  1534. width += cWidth;
  1535. height += cHeight;
  1536. int tabExtent = 0;
  1537. // Calculate how much space the tabs will need, based on the
  1538. // minimum size required to display largest child + content border
  1539. //
  1540. switch(tabPlacement) {
  1541. case LEFT:
  1542. case RIGHT:
  1543. height = Math.max(height, calculateMaxTabHeight(tabPlacement) +
  1544. tabAreaInsets.top + tabAreaInsets.bottom);
  1545. tabExtent = preferredTabAreaWidth(tabPlacement, height);
  1546. width += tabExtent;
  1547. break;
  1548. case TOP:
  1549. case BOTTOM:
  1550. default:
  1551. width = Math.max(width, calculateMaxTabWidth(tabPlacement) +
  1552. tabAreaInsets.left + tabAreaInsets.right);
  1553. tabExtent = preferredTabAreaHeight(tabPlacement, width);
  1554. height += tabExtent;
  1555. }
  1556. return new Dimension(width + insets.left + insets.right,
  1557. height + insets.bottom + insets.top);
  1558. }
  1559. protected int preferredTabAreaHeight(int tabPlacement, int width) {
  1560. FontMetrics metrics = getFontMetrics();
  1561. int tabCount = tabPane.getTabCount();
  1562. int total = 0;
  1563. if (tabCount > 0) {
  1564. int rows = 1;
  1565. int x = 0;
  1566. int maxTabHeight = calculateMaxTabHeight(tabPlacement);
  1567. for (int i = 0; i < tabCount; i++) {
  1568. int tabWidth = calculateTabWidth(tabPlacement, i, metrics);
  1569. if (x != 0 && x + tabWidth > width) {
  1570. rows++;
  1571. x = 0;
  1572. }
  1573. x += tabWidth;
  1574. }
  1575. total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
  1576. }
  1577. return total;
  1578. }
  1579. protected int preferredTabAreaWidth(int tabPlacement, int height) {
  1580. FontMetrics metrics = getFontMetrics();
  1581. int tabCount = tabPane.getTabCount();
  1582. int total = 0;
  1583. if (tabCount > 0) {
  1584. int columns = 1;
  1585. int y = 0;
  1586. int fontHeight = metrics.getHeight();
  1587. maxTabWidth = calculateMaxTabWidth(tabPlacement);
  1588. for (int i = 0; i < tabCount; i++) {
  1589. int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
  1590. if (y != 0 && y + tabHeight > height) {
  1591. columns++;
  1592. y = 0;
  1593. }
  1594. y += tabHeight;
  1595. }
  1596. total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
  1597. }
  1598. return total;
  1599. }
  1600. public void layoutContainer(Container parent) {
  1601. setMouseOverTab(-1);
  1602. int tabPlacement = tabPane.getTabPlacement();
  1603. Insets insets = tabPane.getInsets();
  1604. int selectedIndex = tabPane.getSelectedIndex();
  1605. Component visibleComponent = getVisibleComponent();
  1606. calculateLayoutInfo();
  1607. if (selectedIndex < 0) {
  1608. if (visibleComponent != null) {
  1609. // The last tab was removed, so remove the component
  1610. setVisibleComponent(null);
  1611. }
  1612. } else {
  1613. int cx, cy, cw, ch;
  1614. int totalTabWidth = 0;
  1615. int totalTabHeight = 0;
  1616. Insets contentInsets = getContentBorderInsets(tabPlacement);
  1617. Component selectedComponent = tabPane.getComponentAt(selectedIndex);
  1618. boolean shouldChangeFocus = false;
  1619. // In order to allow programs to use a single component
  1620. // as the display for multiple tabs, we will not change
  1621. // the visible compnent if the currently selected tab
  1622. // has a null component. This is a bit dicey, as we don't
  1623. // explicitly state we support this in the spec, but since
  1624. // programs are now depending on this, we're making it work.
  1625. //
  1626. if (selectedComponent != null) {
  1627. if (selectedComponent != visibleComponent &&
  1628. visibleComponent != null) {
  1629. if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
  1630. shouldChangeFocus = true;
  1631. }
  1632. }
  1633. setVisibleComponent(selectedComponent);
  1634. }
  1635. Rectangle bounds = tabPane.getBounds();
  1636. int numChildren = tabPane.getComponentCount();
  1637. if (numChildren > 0) {
  1638. switch(tabPlacement) {
  1639. case LEFT:
  1640. totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  1641. cx = insets.left + totalTabWidth + contentInsets.left;
  1642. cy = insets.top + contentInsets.top;
  1643. break;
  1644. case RIGHT:
  1645. totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  1646. cx = insets.left + contentInsets.left;
  1647. cy = insets.top + contentInsets.top;
  1648. break;
  1649. case BOTTOM:
  1650. totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  1651. cx = insets.left + contentInsets.left;
  1652. cy = insets.top + contentInsets.top;
  1653. break;
  1654. case TOP:
  1655. default:
  1656. totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  1657. cx = insets.left + contentInsets.left;
  1658. cy = insets.top + totalTabHeight + contentInsets.top;
  1659. }
  1660. cw = bounds.width - totalTabWidth -
  1661. insets.left - insets.right -
  1662. contentInsets.left - contentInsets.right;
  1663. ch = bounds.height - totalTabHeight -
  1664. insets.top - insets.bottom -
  1665. contentInsets.top - contentInsets.bottom;
  1666. for (int i=0; i < numChildren; i++) {
  1667. Component child = tabPane.getComponent(i);
  1668. child.setBounds(cx, cy, cw, ch);
  1669. }
  1670. }
  1671. if (shouldChangeFocus) {
  1672. if (!requestFocusForVisibleComponent()) {
  1673. tabPane.requestFocus();
  1674. }
  1675. }
  1676. }
  1677. }
  1678. public void calculateLayoutInfo() {
  1679. int tabCount = tabPane.getTabCount();
  1680. assureRectsCreated(tabCount);
  1681. calculateTabRects(tabPane.getTabPlacement(), tabCount);
  1682. }
  1683. protected void calculateTabRects(int tabPlacement, int tabCount) {
  1684. FontMetrics metrics = getFontMetrics();
  1685. Dimension size = tabPane.getSize();
  1686. Insets insets = tabPane.getInsets();
  1687. Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
  1688. int fontHeight = metrics.getHeight();
  1689. int selectedIndex = tabPane.getSelectedIndex();
  1690. int tabRunOverlay;
  1691. int i, j;
  1692. int x, y;
  1693. int returnAt;
  1694. boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
  1695. boolean leftToRight = SynthLookAndFeel.isLeftToRight(tabPane);
  1696. //
  1697. // Calculate bounds within which a tab run must fit
  1698. //
  1699. switch(tabPlacement) {
  1700. case LEFT:
  1701. maxTabWidth = calculateMaxTabWidth(tabPlacement);
  1702. x = insets.left + tabAreaInsets.left;
  1703. y = insets.top + tabAreaInsets.top;
  1704. returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
  1705. break;
  1706. case RIGHT:
  1707. maxTabWidth = calculateMaxTabWidth(tabPlacement);
  1708. x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
  1709. y = insets.top + tabAreaInsets.top;
  1710. returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
  1711. break;
  1712. case BOTTOM:
  1713. maxTabHeight = calculateMaxTabHeight(tabPlacement);
  1714. x = insets.left + tabAreaInsets.left;
  1715. y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
  1716. returnAt = size.width - (insets.right + tabAreaInsets.right);
  1717. break;
  1718. case TOP:
  1719. default:
  1720. maxTabHeight = calculateMaxTabHeight(tabPlacement);
  1721. x = insets.left + tabAreaInsets.left;
  1722. y = insets.top + tabAreaInsets.top;
  1723. returnAt = size.width - (insets.right + tabAreaInsets.right);
  1724. break;
  1725. }
  1726. tabRunOverlay = getTabRunOverlay(tabPlacement);
  1727. runCount = 0;
  1728. selectedRun = -1;
  1729. if (tabCount == 0) {
  1730. return;
  1731. }
  1732. // Run through tabs and partition them into runs
  1733. Rectangle rect;
  1734. for (i = 0; i < tabCount; i++) {
  1735. rect = rects[i];
  1736. if (!verticalTabRuns) {
  1737. // Tabs on TOP or BOTTOM....
  1738. if (i > 0) {
  1739. rect.x = rects[i-1].x + rects[i-1].width;
  1740. } else {
  1741. tabRuns[0] = 0;
  1742. runCount = 1;
  1743. maxTabWidth = 0;
  1744. rect.x = x;
  1745. }
  1746. rect.width = calculateTabWidth(tabPlacement, i, metrics);
  1747. maxTabWidth = Math.max(maxTabWidth, rect.width);
  1748. // Never move a TAB down a run if it is in the first column.
  1749. // Even if there isn't enough room, moving it to a fresh
  1750. // line won't help.
  1751. if (rect.x != 2 + insets.left && rect.x + rect.width > returnAt) {
  1752. if (runCount > tabRuns.length - 1) {
  1753. expandTabRunsArray();
  1754. }
  1755. tabRuns[runCount] = i;
  1756. runCount++;
  1757. rect.x = x;
  1758. }
  1759. // Initialize y position in case there's just one run
  1760. rect.y = y;
  1761. rect.height = maxTabHeight/* - 2*/;
  1762. } else {
  1763. // Tabs on LEFT or RIGHT...
  1764. if (i > 0) {
  1765. rect.y = rects[i-1].y + rects[i-1].height;
  1766. } else {
  1767. tabRuns[0] = 0;
  1768. runCount = 1;
  1769. maxTabHeight = 0;
  1770. rect.y = y;
  1771. }
  1772. rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
  1773. maxTabHeight = Math.max(maxTabHeight, rect.height);
  1774. // Never move a TAB over a run if it is in the first run.
  1775. // Even if there isn't enough room, moving it to a fresh
  1776. // column won't help.
  1777. if (rect.y != 2 + insets.top && rect.y + rect.height > returnAt) {
  1778. if (runCount > tabRuns.length - 1) {
  1779. expandTabRunsArray();
  1780. }
  1781. tabRuns[runCount] = i;
  1782. runCount++;
  1783. rect.y = y;
  1784. }
  1785. // Initialize x position in case there's just one column
  1786. rect.x = x;
  1787. rect.width = maxTabWidth/* - 2*/;
  1788. }
  1789. if (i == selectedIndex) {
  1790. selectedRun = runCount - 1;
  1791. }
  1792. }
  1793. if (runCount > 1) {
  1794. // Re-distribute tabs in case last run has leftover space
  1795. normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
  1796. selectedRun = getRunForTab(tabCount, selectedIndex);
  1797. // Rotate run array so that selected run is first
  1798. if (shouldRotateTabRuns(tabPlacement)) {
  1799. rotateTabRuns(tabPlacement, selectedRun);
  1800. }
  1801. }
  1802. // Step through runs from back to front to calculate
  1803. // tab y locations and to pad runs appropriately
  1804. for (i = runCount - 1; i >= 0; i--) {
  1805. int start = tabRuns[i];
  1806. int next = tabRuns[i == (runCount - 1)? 0 : i + 1];
  1807. int end = (next != 0? next - 1 : tabCount - 1);
  1808. if (!verticalTabRuns) {
  1809. for (j = start; j <= end; j++) {
  1810. rect = rects[j];
  1811. rect.y = y;
  1812. rect.x += getTabRunIndent(tabPlacement, i);
  1813. }
  1814. if (shouldPadTabRun(tabPlacement, i)) {
  1815. padTabRun(tabPlacement, start, end, returnAt);
  1816. }
  1817. if (tabPlacement == BOTTOM) {
  1818. y -= (maxTabHeight - tabRunOverlay);
  1819. } else {
  1820. y += (maxTabHeight - tabRunOverlay);
  1821. }
  1822. } else {
  1823. for (j = start; j <= end; j++) {
  1824. rect = rects[j];
  1825. rect.x = x;
  1826. rect.y += getTabRunIndent(tabPlacement, i);
  1827. }
  1828. if (shouldPadTabRun(tabPlacement, i)) {
  1829. padTabRun(tabPlacement, start, end, returnAt);
  1830. }
  1831. if (tabPlacement == RIGHT) {
  1832. x -= (maxTabWidth - tabRunOverlay);
  1833. } else {
  1834. x += (maxTabWidth - tabRunOverlay);
  1835. }
  1836. }
  1837. }
  1838. // Pad the selected tab so that it appears raised in front
  1839. padSelectedTab(tabPlacement, selectedIndex);
  1840. // if right to left and tab placement on the top or
  1841. // the bottom, flip x positions and adjust by widths
  1842. if (!leftToRight && !verticalTabRuns) {
  1843. int rightMargin = size.width
  1844. - (insets.right + tabAreaInsets.right);
  1845. for (i = 0; i < tabCount; i++) {
  1846. rects[i].x = rightMargin - rects[i].x - rects[i].width;
  1847. }
  1848. }
  1849. }
  1850. /*
  1851. * Rotates the run-index array so that the selected run is run[0]
  1852. */
  1853. protected void rotateTabRuns(int tabPlacement, int selectedRun) {
  1854. for (int i = 0; i < selectedRun; i++) {
  1855. int save = tabRuns[0];
  1856. for (int j = 1; j < runCount; j++) {
  1857. tabRuns[j - 1] = tabRuns[j];
  1858. }
  1859. tabRuns[runCount-1] = save;
  1860. }
  1861. }
  1862. protected void normalizeTabRuns(int tabPlacement, int tabCount,
  1863. int start, int max) {
  1864. boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
  1865. int run = runCount - 1;
  1866. boolean keepAdjusting = true;
  1867. double weight = 1.25;
  1868. // At this point the tab runs are packed to fit as many
  1869. // tabs as possible, which can leave the last run with a lot
  1870. // of extra space (resulting in very fat tabs on the last run).
  1871. // So we'll attempt to distribute this extra space more evenly
  1872. // across the runs in order to make the runs look more consistent.
  1873. //
  1874. // Starting with the last run, determine whether the last tab in
  1875. // the previous run would fit (generously) in this run; if so,
  1876. // move tab to current run and shift tabs accordingly. Cycle
  1877. // through remaining runs using the same algorithm.
  1878. //
  1879. while (keepAdjusting) {
  1880. int last = lastTabInRun(tabCount, run);
  1881. int prevLast = lastTabInRun(tabCount, run-1);
  1882. int end;
  1883. int prevLastLen;
  1884. if (!verticalTabRuns) {
  1885. end = rects[last].x + rects[last].width;
  1886. prevLastLen = (int)(maxTabWidth*weight);
  1887. } else {
  1888. end = rects[last].y + rects[last].height;
  1889. prevLastLen = (int)(maxTabHeight*weight*2);
  1890. }
  1891. // Check if the run has enough extra space to fit the last tab
  1892. // from the previous row...
  1893. if (max - end > prevLastLen) {
  1894. // Insert tab from previous row and shift rest over
  1895. tabRuns[run] = prevLast;
  1896. if (!verticalTabRuns) {
  1897. rects[prevLast].x = start;
  1898. } else {
  1899. rects[prevLast].y = start;
  1900. }
  1901. for (int i = prevLast+1; i <= last; i++) {
  1902. if (!verticalTabRuns) {
  1903. rects[i].x = rects[i-1].x + rects[i-1].width;
  1904. } else {
  1905. rects[i].y = rects[i-1].y + rects[i-1].height;
  1906. }
  1907. }
  1908. } else if (run == runCount - 1) {
  1909. // no more room left in last run, so we're done!
  1910. keepAdjusting = false;
  1911. }
  1912. if (run - 1 > 0) {
  1913. // check previous run next...
  1914. run -= 1;
  1915. } else {
  1916. // check last run again...but require a higher ratio
  1917. // of extraspace-to-tabsize because we don't want to
  1918. // end up with too many tabs on the last run!
  1919. run = runCount - 1;
  1920. weight += .25;
  1921. }
  1922. }
  1923. }
  1924. protected void padTabRun(int tabPlacement, int start, int end, int max) {
  1925. Rectangle lastRect = rects[end];
  1926. if (tabPlacement == TOP || tabPlacement == BOTTOM) {
  1927. int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
  1928. int deltaWidth = max - (lastRect.x + lastRect.width);
  1929. float factor = (float)deltaWidth / (float)runWidth;
  1930. for (int j = start; j <= end; j++) {
  1931. Rectangle pastRect = rects[j];
  1932. if (j > start) {
  1933. pastRect.x = rects[j-1].x + rects[j-1].width;
  1934. }
  1935. pastRect.width += Math.round((float)pastRect.width * factor);
  1936. }
  1937. lastRect.width = max - lastRect.x;
  1938. } else {
  1939. int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
  1940. int deltaHeight = max - (lastRect.y + lastRect.height);
  1941. float factor = (float)deltaHeight / (float)runHeight;
  1942. for (int j = start; j <= end; j++) {
  1943. Rectangle pastRect = rects[j];
  1944. if (j > start) {
  1945. pastRect.y = rects[j-1].y + rects[j-1].height;
  1946. }
  1947. pastRect.height += Math.round((float)pastRect.height * factor);
  1948. }
  1949. lastRect.height = max - lastRect.y;
  1950. }
  1951. }
  1952. protected void padSelectedTab(int tabPlacement, int selectedIndex) {
  1953. if (selectedIndex >= 0) {
  1954. Rectangle selRect = rects[selectedIndex];
  1955. Insets padInsets = getSelectedTabPadInsets(tabPlacement);
  1956. selRect.x -= padInsets.left;
  1957. selRect.width += (padInsets.left + padInsets.right);
  1958. selRect.y -= padInsets.top;
  1959. selRect.height += (padInsets.top + padInsets.bottom);
  1960. }
  1961. }
  1962. }
  1963. private class TabbedPaneScrollLayout extends TabbedPaneLayout {
  1964. protected int preferredTabAreaHeight(int tabPlacement, int width) {
  1965. return calculateMaxTabHeight(tabPlacement);
  1966. }
  1967. protected int preferredTabAreaWidth(int tabPlacement, int height) {
  1968. return calculateMaxTabWidth(tabPlacement);
  1969. }
  1970. public void layoutContainer(Container parent) {
  1971. int tabPlacement = tabPane.getTabPlacement();
  1972. int tabCount = tabPane.getTabCount();
  1973. Insets insets = tabPane.getInsets();
  1974. int selectedIndex = tabPane.getSelectedIndex();
  1975. Component visibleComponent = getVisibleComponent();
  1976. calculateLayoutInfo();
  1977. if (selectedIndex < 0) {
  1978. if (visibleComponent != null) {
  1979. // The last tab was removed, so remove the component
  1980. setVisibleComponent(null);
  1981. }
  1982. } else {
  1983. Component selectedComponent = tabPane.getComponentAt(selectedIndex);
  1984. boolean shouldChangeFocus = false;
  1985. // In order to allow programs to use a single component
  1986. // as the display for multiple tabs, we will not change
  1987. // the visible compnent if the currently selected tab
  1988. // has a null component. This is a bit dicey, as we don't
  1989. // explicitly state we support this in the spec, but since
  1990. // programs are now depending on this, we're making it work.
  1991. //
  1992. if (selectedComponent != null) {
  1993. if (selectedComponent != visibleComponent &&
  1994. visibleComponent != null) {
  1995. if (SwingUtilities.findFocusOwner(visibleComponent) != null) {
  1996. shouldChangeFocus = true;
  1997. }
  1998. }
  1999. setVisibleComponent(selectedComponent);
  2000. }
  2001. int tx, ty, tw, th; // tab area bounds
  2002. int cx, cy, cw, ch; // content area bounds
  2003. Insets contentInsets = getContentBorderInsets(tabPlacement);
  2004. Rectangle bounds = tabPane.getBounds();
  2005. int numChildren = tabPane.getComponentCount();
  2006. if (numChildren > 0) {
  2007. switch(tabPlacement) {
  2008. case LEFT:
  2009. // calculate tab area bounds
  2010. tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  2011. th = bounds.height - insets.top - insets.bottom;
  2012. tx = insets.left;
  2013. ty = insets.top;
  2014. // calculate content area bounds
  2015. cx = tx + tw + contentInsets.left;
  2016. cy = ty + contentInsets.top;
  2017. cw = bounds.width - insets.left - insets.right - tw -
  2018. contentInsets.left - contentInsets.right;
  2019. ch = bounds.height - insets.top - insets.bottom -
  2020. contentInsets.top - contentInsets.bottom;
  2021. break;
  2022. case RIGHT:
  2023. // calculate tab area bounds
  2024. tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
  2025. th = bounds.height - insets.top - insets.bottom;
  2026. tx = bounds.width - insets.right - tw;
  2027. ty = insets.top;
  2028. // calculate content area bounds
  2029. cx = insets.left + contentInsets.left;
  2030. cy = insets.top + contentInsets.top;
  2031. cw = bounds.width - insets.left - insets.right - tw -
  2032. contentInsets.left - contentInsets.right;
  2033. ch = bounds.height - insets.top - insets.bottom -
  2034. contentInsets.top - contentInsets.bottom;
  2035. break;
  2036. case BOTTOM:
  2037. // calculate tab area bounds
  2038. tw = bounds.width - insets.left - insets.right;
  2039. th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  2040. tx = insets.left;
  2041. ty = bounds.height - insets.bottom - th;
  2042. // calculate content area bounds
  2043. cx = insets.left + contentInsets.left;
  2044. cy = insets.top + contentInsets.top;
  2045. cw = bounds.width - insets.left - insets.right -
  2046. contentInsets.left - contentInsets.right;
  2047. ch = bounds.height - insets.top - insets.bottom - th -
  2048. contentInsets.top - contentInsets.bottom;
  2049. break;
  2050. case TOP:
  2051. default:
  2052. // calculate tab area bounds
  2053. tw = bounds.width - insets.left - insets.right;
  2054. th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
  2055. tx = insets.left;
  2056. ty = insets.top;
  2057. // calculate content area bounds
  2058. cx = tx + contentInsets.left;
  2059. cy = ty + th + contentInsets.top;
  2060. cw = bounds.width - insets.left - insets.right -
  2061. contentInsets.left - contentInsets.right;
  2062. ch = bounds.height - insets.top - insets.bottom - th -
  2063. contentInsets.top - contentInsets.bottom;
  2064. }
  2065. for (int i=0; i < numChildren; i++) {
  2066. Component child = tabPane.getComponent(i);
  2067. if (child instanceof ScrollableTabViewport) {
  2068. JViewport viewport = (JViewport)child;
  2069. Rectangle viewRect = viewport.getViewRect();
  2070. int vw = tw;
  2071. int vh = th;
  2072. switch(tabPlacement) {
  2073. case LEFT:
  2074. case RIGHT:
  2075. int totalTabHeight = rects[tabCount-1].y + rects[tabCount-1].height;
  2076. if (totalTabHeight > th) {
  2077. // Allow space for scrollbuttons
  2078. vh = Math.max(th - 36, 36);
  2079. if (totalTabHeight - viewRect.y <= vh) {
  2080. // Scrolled to the end, so ensure the viewport size is
  2081. // such that the scroll offset aligns with a tab
  2082. vh = totalTabHeight - viewRect.y;
  2083. }
  2084. }
  2085. break;
  2086. case BOTTOM:
  2087. case TOP:
  2088. default:
  2089. int totalTabWidth = rects[tabCount-1].x + rects[tabCount-1].width;
  2090. if (totalTabWidth > tw) {
  2091. // Need to allow space for scrollbuttons
  2092. vw = Math.max(tw - 36, 36);;
  2093. if (totalTabWidth - viewRect.x <= vw) {
  2094. // Scrolled to the end, so ensure the viewport size is
  2095. // such that the scroll offset aligns with a tab
  2096. vw = totalTabWidth - viewRect.x;
  2097. }
  2098. }
  2099. }
  2100. child.setBounds(tx, ty, vw, vh);
  2101. } else if (child instanceof ScrollableTabButton) {
  2102. ScrollableTabButton scrollbutton = (ScrollableTabButton)child;
  2103. Dimension bsize = scrollbutton.getPreferredSize();
  2104. int bx = 0;
  2105. int by = 0;
  2106. int bw = bsize.width;
  2107. int bh = bsize.height;
  2108. boolean visible = false;
  2109. switch(tabPlacement) {
  2110. case LEFT:
  2111. case RIGHT:
  2112. int totalTabHeight = rects[tabCount-1].y + rects[tabCount-1].height;
  2113. if (totalTabHeight > th) {
  2114. int dir = scrollbutton.scrollsForward()? SOUTH : NORTH;
  2115. scrollbutton.setDirection(dir);
  2116. visible = true;
  2117. bx = (tabPlacement == LEFT? tx + tw - bsize.width : tx);
  2118. by = dir == SOUTH?
  2119. bounds.height - insets.bottom - bsize.height :
  2120. bounds.height - insets.bottom - 2*bsize.height;
  2121. }
  2122. break;
  2123. case BOTTOM:
  2124. case TOP:
  2125. default:
  2126. int totalTabWidth = rects[tabCount-1].x + rects[tabCount-1].width;
  2127. if (totalTabWidth > tw) {
  2128. int dir = scrollbutton.scrollsForward()? EAST : WEST;
  2129. scrollbutton.setDirection(dir);
  2130. visible = true;
  2131. bx = dir == EAST?
  2132. bounds.width - insets.left - bsize.width :
  2133. bounds.width - insets.left - 2*bsize.width;
  2134. by = (tabPlacement == TOP? ty + th - bsize.height : ty);
  2135. }
  2136. }
  2137. child.setVisible(visible);
  2138. if (visible) {
  2139. child.setBounds(bx, by, bw, bh);
  2140. }
  2141. } else {
  2142. // All content children...
  2143. child.setBounds(cx, cy, cw, ch);
  2144. }
  2145. }
  2146. if (shouldChangeFocus) {
  2147. if (!requestFocusForVisibleComponent()) {
  2148. tabPane.requestFocus();
  2149. }
  2150. }
  2151. }
  2152. }
  2153. }
  2154. protected void calculateTabRects(int tabPlacement, int tabCount) {
  2155. FontMetrics metrics = getFontMetrics();
  2156. Dimension size = tabPane.getSize();
  2157. Insets insets = tabPane.getInsets();
  2158. Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
  2159. int fontHeight = metrics.getHeight();
  2160. int selectedIndex = tabPane.getSelectedIndex();
  2161. int i, j;
  2162. boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
  2163. boolean leftToRight = SynthLookAndFeel.isLeftToRight(tabPane);
  2164. int x = tabAreaInsets.left;
  2165. int y = tabAreaInsets.top;
  2166. int totalWidth = 0;
  2167. int totalHeight = 0;
  2168. //
  2169. // Calculate bounds within which a tab run must fit
  2170. //
  2171. switch(tabPlacement) {
  2172. case LEFT:
  2173. case RIGHT:
  2174. maxTabWidth = calculateMaxTabWidth(tabPlacement);
  2175. break;
  2176. case BOTTOM:
  2177. case TOP:
  2178. default:
  2179. maxTabHeight = calculateMaxTabHeight(tabPlacement);
  2180. }
  2181. runCount = 0;
  2182. selectedRun = -1;
  2183. if (tabCount == 0) {
  2184. return;
  2185. }
  2186. selectedRun = 0;
  2187. runCount = 1;
  2188. // Run through tabs and lay them out in a single run
  2189. Rectangle rect;
  2190. for (i = 0; i < tabCount; i++) {
  2191. rect = rects[i];
  2192. if (!verticalTabRuns) {
  2193. // Tabs on TOP or BOTTOM....
  2194. if (i > 0) {
  2195. rect.x = rects[i-1].x + rects[i-1].width;
  2196. } else {
  2197. tabRuns[0] = 0;
  2198. maxTabWidth = 0;
  2199. totalHeight += maxTabHeight;
  2200. rect.x = x;
  2201. }
  2202. rect.width = calculateTabWidth(tabPlacement, i, metrics);
  2203. totalWidth = rect.x + rect.width;
  2204. maxTabWidth = Math.max(maxTabWidth, rect.width);
  2205. rect.y = y;
  2206. rect.height = maxTabHeight/* - 2*/;
  2207. } else {
  2208. // Tabs on LEFT or RIGHT...
  2209. if (i > 0) {
  2210. rect.y = rects[i-1].y + rects[i-1].height;
  2211. } else {
  2212. tabRuns[0] = 0;
  2213. maxTabHeight = 0;
  2214. totalWidth = maxTabWidth;
  2215. rect.y = y;
  2216. }
  2217. rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
  2218. totalHeight = rect.y + rect.height;
  2219. maxTabHeight = Math.max(maxTabHeight, rect.height);
  2220. rect.x = x;
  2221. rect.width = maxTabWidth/* - 2*/;
  2222. }
  2223. }
  2224. // if right to left and tab placement on the top or
  2225. // the bottom, flip x positions and adjust by widths
  2226. if (!leftToRight && !verticalTabRuns) {
  2227. int rightMargin = size.width
  2228. - (insets.right + tabAreaInsets.right);
  2229. for (i = 0; i < tabCount; i++) {
  2230. rects[i].x = rightMargin - rects[i].x - rects[i].width;
  2231. }
  2232. }
  2233. //tabPanel.setSize(totalWidth, totalHeight);
  2234. tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
  2235. }
  2236. }
  2237. private class ScrollableTabSupport implements ChangeListener {
  2238. public ScrollableTabViewport viewport;
  2239. public ScrollableTabPanel tabPanel;
  2240. public ScrollableTabButton scrollForwardButton;
  2241. public ScrollableTabButton scrollBackwardButton;
  2242. public int leadingTabIndex;
  2243. private Point tabViewPosition = new Point(0,0);
  2244. ScrollableTabSupport(int tabPlacement) {
  2245. viewport = new ScrollableTabViewport();
  2246. tabPanel = new ScrollableTabPanel();
  2247. viewport.setView(tabPanel);
  2248. viewport.addChangeListener(this);
  2249. if (tabPlacement == TOP || tabPlacement == BOTTOM) {
  2250. scrollForwardButton = new ScrollableTabButton(EAST);
  2251. scrollBackwardButton = new ScrollableTabButton(WEST);
  2252. } else { // tabPlacement = LEFT || RIGHT
  2253. scrollForwardButton = new ScrollableTabButton(SOUTH);
  2254. scrollBackwardButton = new ScrollableTabButton(NORTH);
  2255. }
  2256. }
  2257. public void scrollForward(int tabPlacement) {
  2258. Dimension viewSize = viewport.getViewSize();
  2259. Rectangle viewRect = viewport.getViewRect();
  2260. if (tabPlacement == TOP || tabPlacement == BOTTOM) {
  2261. if (viewRect.width >= viewSize.width - viewRect.x) {
  2262. return; // no room left to scroll
  2263. }
  2264. } else { // tabPlacement == LEFT || tabPlacement == RIGHT
  2265. if (viewRect.height >= viewSize.height - viewRect.y) {
  2266. return;
  2267. }
  2268. }
  2269. setLeadingTabIndex(tabPlacement, leadingTabIndex+1);
  2270. }
  2271. public void scrollBackward(int tabPlacement) {
  2272. if (leadingTabIndex == 0) {
  2273. return; // no room left to scroll
  2274. }
  2275. setLeadingTabIndex(tabPlacement, leadingTabIndex-1);
  2276. }
  2277. public void setLeadingTabIndex(int tabPlacement, int index) {
  2278. leadingTabIndex = index;
  2279. Dimension viewSize = viewport.getViewSize();
  2280. Rectangle viewRect = viewport.getViewRect();
  2281. switch(tabPlacement) {
  2282. case TOP:
  2283. case BOTTOM:
  2284. tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x;
  2285. if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
  2286. // We've scrolled to the end, so adjust the viewport size
  2287. // to ensure the view position remains aligned on a tab boundary
  2288. Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x,
  2289. viewRect.height);
  2290. viewport.setExtentSize(extentSize);
  2291. }
  2292. break;
  2293. case LEFT:
  2294. case RIGHT:
  2295. tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y;
  2296. if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
  2297. // We've scrolled to the end, so adjust the viewport size
  2298. // to ensure the view position remains aligned on a tab boundary
  2299. Dimension extentSize = new Dimension(viewRect.width,
  2300. viewSize.height - tabViewPosition.y);
  2301. viewport.setExtentSize(extentSize);
  2302. }
  2303. }
  2304. viewport.setViewPosition(tabViewPosition);
  2305. }
  2306. public void stateChanged(ChangeEvent e) {
  2307. JViewport viewport = (JViewport)e.getSource();
  2308. int tabPlacement = tabPane.getTabPlacement();
  2309. int tabCount = tabPane.getTabCount();
  2310. Rectangle vpRect = viewport.getBounds();
  2311. Dimension viewSize = viewport.getViewSize();
  2312. Rectangle viewRect = viewport.getViewRect();
  2313. leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
  2314. // If the tab isn't right aligned, adjust it.
  2315. if (leadingTabIndex + 1 < tabCount) {
  2316. switch (tabPlacement) {
  2317. case TOP:
  2318. case BOTTOM:
  2319. if (rects[leadingTabIndex].x < viewRect.x) {
  2320. leadingTabIndex++;
  2321. }
  2322. break;
  2323. case LEFT:
  2324. case RIGHT:
  2325. if (rects[leadingTabIndex].y < viewRect.y) {
  2326. leadingTabIndex++;
  2327. }
  2328. break;
  2329. }
  2330. }
  2331. Insets contentInsets = getContentBorderInsets(tabPlacement);
  2332. switch(tabPlacement) {
  2333. case LEFT:
  2334. tabPane.repaint(vpRect.x+vpRect.width, vpRect.y,
  2335. contentInsets.left, vpRect.height);
  2336. scrollBackwardButton.setEnabled(viewRect.y > 0);
  2337. scrollForwardButton.setEnabled(leadingTabIndex < tabCount-1 &&
  2338. viewSize.height-viewRect.y > viewRect.height);
  2339. break;
  2340. case RIGHT:
  2341. tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y,
  2342. contentInsets.right, vpRect.height);
  2343. scrollBackwardButton.setEnabled(viewRect.y > 0);
  2344. scrollForwardButton.setEnabled(leadingTabIndex < tabCount-1 &&
  2345. viewSize.height-viewRect.y > viewRect.height);
  2346. break;
  2347. case BOTTOM:
  2348. tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom,
  2349. vpRect.width, contentInsets.bottom);
  2350. scrollBackwardButton.setEnabled(viewRect.x > 0);
  2351. scrollForwardButton.setEnabled(leadingTabIndex < tabCount-1 &&
  2352. viewSize.width-viewRect.x > viewRect.width);
  2353. break;
  2354. case TOP:
  2355. default:
  2356. tabPane.repaint(vpRect.x, vpRect.y+vpRect.height,
  2357. vpRect.width, contentInsets.top);
  2358. scrollBackwardButton.setEnabled(viewRect.x > 0);
  2359. scrollForwardButton.setEnabled(leadingTabIndex < tabCount-1 &&
  2360. viewSize.width-viewRect.x > viewRect.width);
  2361. }
  2362. }
  2363. public String toString() {
  2364. return new String("viewport.viewSize="+viewport.getViewSize()+"\n"+
  2365. "viewport.viewRectangle="+viewport.getViewRect()+"\n"+
  2366. "leadingTabIndex="+leadingTabIndex+"\n"+
  2367. "tabViewPosition="+tabViewPosition);
  2368. }
  2369. }
  2370. private class ScrollableTabViewport extends JViewport implements UIResource {
  2371. public ScrollableTabViewport() {
  2372. super();
  2373. setScrollMode(SIMPLE_SCROLL_MODE);
  2374. }
  2375. }
  2376. private class ScrollableTabPanel extends JPanel implements UIResource {
  2377. public ScrollableTabPanel() {
  2378. setLayout(null);
  2379. }
  2380. public void paintComponent(Graphics g) {
  2381. super.paintComponent(g);
  2382. SynthTabbedPaneUI.this.paintTabArea(
  2383. tabAreaContext, g, tabPane.getTabPlacement(),
  2384. tabPane.getSelectedIndex(), getBounds());
  2385. }
  2386. }
  2387. private class ScrollableTabButton extends SynthArrowButton implements
  2388. UIResource, SwingConstants {
  2389. public ScrollableTabButton(int direction) {
  2390. super(direction);
  2391. }
  2392. public boolean scrollsForward() {
  2393. return getDirection() == EAST || getDirection() == SOUTH;
  2394. }
  2395. }
  2396. // Controller: event listeners
  2397. /**
  2398. * This inner class is marked "public" due to a compiler bug.
  2399. * This class should be treated as a "protected" inner class.
  2400. * Instantiate it only within subclasses of SynthTabbedPaneUI.
  2401. */
  2402. class PropertyChangeHandler implements PropertyChangeListener {
  2403. public void propertyChange(PropertyChangeEvent e) {
  2404. JTabbedPane pane = (JTabbedPane)e.getSource();
  2405. String name = e.getPropertyName();
  2406. if (SynthLookAndFeel.shouldUpdateStyle(e)) {
  2407. fetchStyle(pane);
  2408. }
  2409. if ("mnemonicAt".equals(name)) {
  2410. updateMnemonics();
  2411. pane.repaint();
  2412. }
  2413. else if ("displayedMnemonicIndexAt".equals(name)) {
  2414. pane.repaint();
  2415. }
  2416. else if ( name.equals("indexForTitle") ) {
  2417. int index = ((Integer)e.getNewValue()).intValue();
  2418. String title = tabPane.getTitleAt(index);
  2419. if (BasicHTML.isHTMLString(title)) {
  2420. if (htmlViews==null) { // Initialize vector
  2421. htmlViews = createHTMLVector();
  2422. } else { // Vector already exists
  2423. View v = BasicHTML.createHTMLView(tabPane, title);
  2424. htmlViews.setElementAt(v, index);
  2425. }
  2426. } else {
  2427. if (htmlViews != null && htmlViews.elementAt(index) != null) {
  2428. htmlViews.setElementAt(null, index);
  2429. }
  2430. }
  2431. updateMnemonics();
  2432. } else if (name.equals("tabLayoutPolicy")) {
  2433. // PENDING: YIKES!
  2434. SynthTabbedPaneUI.this.uninstallUI(pane);
  2435. SynthTabbedPaneUI.this.installUI(pane);
  2436. }
  2437. }
  2438. }
  2439. /**
  2440. * This inner class is marked "public" due to a compiler bug.
  2441. * This class should be treated as a "protected" inner class.
  2442. * Instantiate it only within subclasses of SynthTabbedPaneUI.
  2443. */
  2444. class TabSelectionHandler implements ChangeListener {
  2445. public void stateChanged(ChangeEvent e) {
  2446. JTabbedPane tabPane = (JTabbedPane)e.getSource();
  2447. tabPane.revalidate();
  2448. tabPane.repaint();
  2449. if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
  2450. int index = tabPane.getSelectedIndex();
  2451. if (index < rects.length && index != -1) {
  2452. tabScroller.tabPanel.scrollRectToVisible(rects[index]);
  2453. }
  2454. }
  2455. }
  2456. }
  2457. /**
  2458. * This inner class is marked "public" due to a compiler bug.
  2459. * This class should be treated as a "protected" inner class.
  2460. * Instantiate it only within subclasses of SynthTabbedPaneUI.
  2461. */
  2462. class MouseHandler extends MouseInputAdapter {
  2463. public void mousePressed(MouseEvent e) {
  2464. if (!tabPane.isEnabled()) {
  2465. return;
  2466. }
  2467. int tabIndex = getTabAtLocation(e.getX(), e.getY());
  2468. if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
  2469. if (tabIndex != tabPane.getSelectedIndex()) {
  2470. tabPane.setSelectedIndex(tabIndex);
  2471. }
  2472. if (tabPane.isRequestFocusEnabled() && tabIndex != focusIndex) {
  2473. tabPane.requestFocus();
  2474. int oldFocusIndex = focusIndex;
  2475. setFocusIndex(tabIndex);
  2476. tabPane.repaint(getTabBounds(tabPane, focusIndex));
  2477. if (oldFocusIndex != -1) {
  2478. tabPane.repaint(getTabBounds(tabPane, oldFocusIndex));
  2479. }
  2480. }
  2481. }
  2482. }
  2483. public void mouseEntered(MouseEvent e) {
  2484. updateMouseOver(e.getX(), e.getY());
  2485. }
  2486. public void mouseMoved(MouseEvent e) {
  2487. updateMouseOver(e.getX(), e.getY());
  2488. }
  2489. public void mouseExited(MouseEvent e) {
  2490. setMouseOverTab(-1);
  2491. }
  2492. }
  2493. /**
  2494. * This inner class is marked "public" due to a compiler bug.
  2495. * This class should be treated as a "protected" inner class.
  2496. * Instantiate it only within subclasses of SynthTabbedPaneUI.
  2497. */
  2498. class FocusHandler extends FocusAdapter {
  2499. public void focusGained(FocusEvent e) {
  2500. JTabbedPane tabPane = (JTabbedPane)e.getSource();
  2501. int tabCount = tabPane.getTabCount();
  2502. if (focusIndex == -1) {
  2503. setFocusIndex(tabPane.getSelectedIndex());
  2504. }
  2505. if (focusIndex != -1 && tabCount > 0
  2506. && tabCount == rects.length) {
  2507. tabPane.repaint(getTabBounds(tabPane, focusIndex));
  2508. }
  2509. }
  2510. public void focusLost(FocusEvent e) {
  2511. JTabbedPane tabPane = (JTabbedPane)e.getSource();
  2512. int tabCount = tabPane.getTabCount();
  2513. if (focusIndex != -1 && tabCount > 0
  2514. && tabCount == rects.length) {
  2515. //PENDING(aim): this gets called unexplicably when an unselected
  2516. // tab is clicked when the tabbedpane doesn't have focus.
  2517. // need to investigate further!
  2518. tabPane.repaint(getTabBounds(tabPane, focusIndex));
  2519. setFocusIndex(-1);
  2520. }
  2521. }
  2522. }
  2523. /* GES 2/3/99:
  2524. The container listener code was added to support HTML
  2525. rendering of tab titles.
  2526. Ideally, we would be able to listen for property changes
  2527. when a tab is added or its text modified. At the moment
  2528. there are no such events because the Beans spec doesn't
  2529. allow 'indexed' property changes (i.e. tab 2's text changed
  2530. from A to B).
  2531. In order to get around this, we listen for tabs to be added
  2532. or removed by listening for the container events. we then
  2533. queue up a runnable (so the component has a chance to complete
  2534. the add) which checks the tab title of the new component to see
  2535. if it requires HTML rendering.
  2536. The Views (one per tab title requiring HTML rendering) are
  2537. stored in the htmlViews Vector, which is only allocated after
  2538. the first time we run into an HTML tab. Note that this vector
  2539. is kept in step with the number of pages, and nulls are added
  2540. for those pages whose tab title do not require HTML rendering.
  2541. This makes it easy for the paint and layout code to tell
  2542. whether to invoke the HTML engine without having to check
  2543. the string during time-sensitive operations.
  2544. When we have added a way to listen for tab additions and
  2545. changes to tab text, this code should be removed and
  2546. replaced by something which uses that. */
  2547. private class ContainerHandler implements ContainerListener {
  2548. public void componentAdded(ContainerEvent e) {
  2549. JTabbedPane tp = (JTabbedPane)e.getContainer();
  2550. Component child = e.getChild();
  2551. if (child instanceof UIResource) {
  2552. return;
  2553. }
  2554. int index = tp.indexOfComponent(child);
  2555. String title = tp.getTitleAt(index);
  2556. boolean isHTML = BasicHTML.isHTMLString(title);
  2557. if (isHTML) {
  2558. if (htmlViews==null) { // Initialize vector
  2559. htmlViews = createHTMLVector();
  2560. } else { // Vector already exists
  2561. View v = BasicHTML.createHTMLView(tp, title);
  2562. htmlViews.insertElementAt(v, index);
  2563. }
  2564. } else { // Not HTML
  2565. if (htmlViews!=null) { // Add placeholder
  2566. htmlViews.insertElementAt(null, index);
  2567. } // else nada!
  2568. }
  2569. }
  2570. public void componentRemoved(ContainerEvent e) {
  2571. JTabbedPane tp = (JTabbedPane)e.getContainer();
  2572. Component child = e.getChild();
  2573. if (child instanceof UIResource) {
  2574. return;
  2575. }
  2576. // NOTE 4/15/2002 (joutwate):
  2577. // This fix is implemented using client properties since there is
  2578. // currently no IndexPropertyChangeEvent. Once
  2579. // IndexPropertyChangeEvents have been added this code should be
  2580. // modified to use it.
  2581. Integer indexObj =
  2582. (Integer)tp.getClientProperty("__index_to_remove__");
  2583. if (indexObj != null) {
  2584. int index = indexObj.intValue();
  2585. if (htmlViews != null && htmlViews.size()>=index) {
  2586. htmlViews.removeElementAt(index);
  2587. }
  2588. }
  2589. }
  2590. }
  2591. private Vector createHTMLVector() {
  2592. Vector htmlViews = new Vector();
  2593. int count = tabPane.getTabCount();
  2594. if (count>0) {
  2595. for (int i=0 ; i<count; i++) {
  2596. String title = tabPane.getTitleAt(i);
  2597. if (BasicHTML.isHTMLString(title)) {
  2598. htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
  2599. } else {
  2600. htmlViews.addElement(null);
  2601. }
  2602. }
  2603. }
  2604. return htmlViews;
  2605. }
  2606. }