1. /*
  2. * @(#)JTabbedPane.java 1.131 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.beans.*;
  11. import java.util.*;
  12. import javax.swing.event.*;
  13. import javax.swing.plaf.*;
  14. import javax.accessibility.*;
  15. import java.io.Serializable;
  16. import java.io.ObjectOutputStream;
  17. import java.io.ObjectInputStream;
  18. import java.io.IOException;
  19. /**
  20. * A component that lets the user switch between a group of components by
  21. * clicking on a tab with a given title and/or icon.
  22. * For examples and information on using tabbed panes see
  23. * <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/tabbedpane.html">How to Use Tabbed Panes</a>,
  24. * a section in <em>The Java Tutorial</em>.
  25. * <p>
  26. * Tabs/components are added to a <code>TabbedPane</code> object by using the
  27. * <code>addTab</code> and <code>insertTab</code> methods.
  28. * A tab is represented by an index corresponding
  29. * to the position it was added in, where the first tab has an index equal to 0
  30. * and the last tab has an index equal to the tab count minus 1.
  31. * <p>
  32. * The <code>TabbedPane</code> uses a <code>SingleSelectionModel</code>
  33. * to represent the set
  34. * of tab indices and the currently selected index. If the tab count
  35. * is greater than 0, then there will always be a selected index, which
  36. * by default will be initialized to the first tab. If the tab count is
  37. * 0, then the selected index will be -1.
  38. * <p>
  39. * For the keyboard keys used by this component in the standard Look and
  40. * Feel (L&F) renditions, see the
  41. * <a href="doc-files/Key-Index.html#JTabbedPane"><code>JTabbedPane</code> key assignments</a>.
  42. * <p>
  43. * <strong>Warning:</strong>
  44. * Serialized objects of this class will not be compatible with
  45. * future Swing releases. The current serialization support is
  46. * appropriate for short term storage or RMI between applications running
  47. * the same version of Swing. As of 1.4, support for long term storage
  48. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  49. * has been added to the <code>java.beans</code> package.
  50. * Please see {@link java.beans.XMLEncoder}.
  51. *
  52. * @beaninfo
  53. * attribute: isContainer true
  54. * description: A component which provides a tab folder metaphor for
  55. * displaying one component from a set of components.
  56. *
  57. * @version 1.131 01/23/03
  58. * @author Dave Moore
  59. * @author Philip Milne
  60. * @author Amy Fowler
  61. *
  62. * @see SingleSelectionModel
  63. */
  64. public class JTabbedPane extends JComponent
  65. implements Serializable, Accessible, SwingConstants {
  66. /**
  67. * The tab layout policy for wrapping tabs in multiple runs when all
  68. * tabs will not fit within a single run.
  69. */
  70. public static final int WRAP_TAB_LAYOUT = 0;
  71. /**
  72. * Tab layout policy for providing a subset of available tabs when all
  73. * the tabs will not fit within a single run. If all the tabs do
  74. * not fit within a single run the look and feel will provide a way
  75. * to navigate to hidden tabs.
  76. */
  77. public static final int SCROLL_TAB_LAYOUT = 1;
  78. /**
  79. * @see #getUIClassID
  80. * @see #readObject
  81. */
  82. private static final String uiClassID = "TabbedPaneUI";
  83. /**
  84. * Where the tabs are placed.
  85. * @see #setTabPlacement
  86. */
  87. protected int tabPlacement = TOP;
  88. private int tabLayoutPolicy;
  89. /** The default selection model */
  90. protected SingleSelectionModel model;
  91. private boolean haveRegistered;
  92. /**
  93. * The <code>changeListener</code> is the listener we add to the
  94. * model.
  95. */
  96. protected ChangeListener changeListener = null;
  97. Vector pages;
  98. /**
  99. * Only one <code>ChangeEvent</code> is needed per <code>TabPane</code>
  100. * instance since the
  101. * event's only (read-only) state is the source property. The source
  102. * of events generated here is always "this".
  103. */
  104. protected transient ChangeEvent changeEvent = null;
  105. /**
  106. * Creates an empty <code>TabbedPane</code> with a default
  107. * tab placement of <code>JTabbedPane.TOP</code>.
  108. * @see #addTab
  109. */
  110. public JTabbedPane() {
  111. this(TOP, WRAP_TAB_LAYOUT);
  112. }
  113. /**
  114. * Creates an empty <code>TabbedPane</code> with the specified tab placement
  115. * of either: <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
  116. * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
  117. *
  118. * @param tabPlacement the placement for the tabs relative to the content
  119. * @see #addTab
  120. */
  121. public JTabbedPane(int tabPlacement) {
  122. this(tabPlacement, WRAP_TAB_LAYOUT);
  123. }
  124. /**
  125. * Creates an empty <code>TabbedPane</code> with the specified tab placement
  126. * and tab layout policy. Tab placement may be either:
  127. * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
  128. * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
  129. * Tab layout policy may be either: <code>JTabbedPane.WRAP_TAB_LAYOUT</code>
  130. * or <code>JTabbedPane.SCROLL_TAB_LAYOUT</code>.
  131. *
  132. * @param tabPlacement the placement for the tabs relative to the content
  133. * @param tabLayoutPolicy the policy for laying out tabs when all tabs will not fit on one run
  134. * @exception IllegalArgumentException if tab placement or tab layout policy are not
  135. * one of the above supported values
  136. * @see #addTab
  137. * @since 1.4
  138. */
  139. public JTabbedPane(int tabPlacement, int tabLayoutPolicy) {
  140. setTabPlacement(tabPlacement);
  141. setTabLayoutPolicy(tabLayoutPolicy);
  142. pages = new Vector(1);
  143. setModel(new DefaultSingleSelectionModel());
  144. updateUI();
  145. }
  146. /**
  147. * Returns the UI object which implements the L&F for this component.
  148. *
  149. * @return a <code>TabbedPaneUI</code> object
  150. * @see #setUI
  151. */
  152. public TabbedPaneUI getUI() {
  153. return (TabbedPaneUI)ui;
  154. }
  155. /**
  156. * Sets the UI object which implements the L&F for this component.
  157. *
  158. * @param ui the new UI object
  159. * @see UIDefaults#getUI
  160. * @beaninfo
  161. * bound: true
  162. * hidden: true
  163. * attribute: visualUpdate true
  164. * description: The UI object that implements the tabbedpane's LookAndFeel
  165. */
  166. public void setUI(TabbedPaneUI ui) {
  167. super.setUI(ui);
  168. }
  169. /**
  170. * Resets the UI property to a value from the current look and feel.
  171. *
  172. * @see JComponent#updateUI
  173. */
  174. public void updateUI() {
  175. setUI((TabbedPaneUI)UIManager.getUI(this));
  176. }
  177. /**
  178. * Returns the name of the UI class that implements the
  179. * L&F for this component.
  180. *
  181. * @return the string "TabbedPaneUI"
  182. * @see JComponent#getUIClassID
  183. * @see UIDefaults#getUI
  184. */
  185. public String getUIClassID() {
  186. return uiClassID;
  187. }
  188. /**
  189. * We pass <code>ModelChanged</code> events along to the listeners with
  190. * the tabbedpane (instead of the model itself) as the event source.
  191. */
  192. protected class ModelListener implements ChangeListener, Serializable {
  193. public void stateChanged(ChangeEvent e) {
  194. fireStateChanged();
  195. }
  196. }
  197. /**
  198. * Subclasses that want to handle <code>ChangeEvents</code> differently
  199. * can override this to return a subclass of <code>ModelListener</code> or
  200. * another <code>ChangeListener</code> implementation.
  201. *
  202. * @see #fireStateChanged
  203. */
  204. protected ChangeListener createChangeListener() {
  205. return new ModelListener();
  206. }
  207. /**
  208. * Adds a <code>ChangeListener</code> to this tabbedpane.
  209. *
  210. * @param l the <code>ChangeListener</code> to add
  211. * @see #fireStateChanged
  212. * @see #removeChangeListener
  213. */
  214. public void addChangeListener(ChangeListener l) {
  215. listenerList.add(ChangeListener.class, l);
  216. }
  217. /**
  218. * Removes a <code>ChangeListener</code> from this tabbedpane.
  219. *
  220. * @param l the <code>ChangeListener</code> to remove
  221. * @see #fireStateChanged
  222. * @see #addChangeListener
  223. */
  224. public void removeChangeListener(ChangeListener l) {
  225. listenerList.remove(ChangeListener.class, l);
  226. }
  227. /**
  228. * Returns an array of all the <code>ChangeListener</code>s added
  229. * to this <code>JTabbedPane</code> with <code>addChangeListener</code>.
  230. *
  231. * @return all of the <code>ChangeListener</code>s added or an empty
  232. * array if no listeners have been added
  233. * @since 1.4
  234. */
  235. public ChangeListener[] getChangeListeners() {
  236. return (ChangeListener[])listenerList.getListeners(
  237. ChangeListener.class);
  238. }
  239. /**
  240. * Sends a <code>ChangeEvent</code>, whose source is this tabbedpane,
  241. * to each listener. This method method is called each time
  242. * a <code>ChangeEvent</code> is received from the model.
  243. *
  244. * @see #addChangeListener
  245. * @see EventListenerList
  246. */
  247. protected void fireStateChanged() {
  248. // Guaranteed to return a non-null array
  249. Object[] listeners = listenerList.getListenerList();
  250. // Process the listeners last to first, notifying
  251. // those that are interested in this event
  252. for (int i = listeners.length-2; i>=0; i-=2) {
  253. if (listeners[i]==ChangeListener.class) {
  254. // Lazily create the event:
  255. if (changeEvent == null)
  256. changeEvent = new ChangeEvent(this);
  257. ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
  258. }
  259. }
  260. }
  261. /**
  262. * Returns the model associated with this tabbedpane.
  263. *
  264. * @see #setModel
  265. */
  266. public SingleSelectionModel getModel() {
  267. return model;
  268. }
  269. /**
  270. * Sets the model to be used with this tabbedpane.
  271. *
  272. * @param model the model to be used
  273. * @see #getModel
  274. * @beaninfo
  275. * bound: true
  276. * description: The tabbedpane's SingleSelectionModel.
  277. */
  278. public void setModel(SingleSelectionModel model) {
  279. SingleSelectionModel oldModel = getModel();
  280. if (oldModel != null) {
  281. oldModel.removeChangeListener(changeListener);
  282. changeListener = null;
  283. }
  284. this.model = model;
  285. if (model != null) {
  286. changeListener = createChangeListener();
  287. model.addChangeListener(changeListener);
  288. }
  289. firePropertyChange("model", oldModel, model);
  290. repaint();
  291. }
  292. /**
  293. * Returns the placement of the tabs for this tabbedpane.
  294. * @see #setTabPlacement
  295. */
  296. public int getTabPlacement() {
  297. return tabPlacement;
  298. }
  299. /**
  300. * Sets the tab placement for this tabbedpane.
  301. * Possible values are:<ul>
  302. * <li><code>JTabbedPane.TOP</code>
  303. * <li><code>JTabbedPane.BOTTOM</code>
  304. * <li><code>JTabbedPane.LEFT</code>
  305. * <li><code>JTabbedPane.RIGHT</code>
  306. * </ul>
  307. * The default value, if not set, is <code>SwingConstants.TOP</code>.
  308. *
  309. * @param tabPlacement the placement for the tabs relative to the content
  310. * @exception IllegalArgumentException if tab placement value isn't one
  311. * of the above valid values
  312. *
  313. * @beaninfo
  314. * preferred: true
  315. * bound: true
  316. * attribute: visualUpdate true
  317. * enum: TOP JTabbedPane.TOP
  318. * LEFT JTabbedPane.LEFT
  319. * BOTTOM JTabbedPane.BOTTOM
  320. * RIGHT JTabbedPane.RIGHT
  321. * description: The tabbedpane's tab placement.
  322. *
  323. */
  324. public void setTabPlacement(int tabPlacement) {
  325. if (tabPlacement != TOP && tabPlacement != LEFT &&
  326. tabPlacement != BOTTOM && tabPlacement != RIGHT) {
  327. throw new IllegalArgumentException("illegal tab placement: must be TOP, BOTTOM, LEFT, or RIGHT");
  328. }
  329. if (this.tabPlacement != tabPlacement) {
  330. int oldValue = this.tabPlacement;
  331. this.tabPlacement = tabPlacement;
  332. firePropertyChange("tabPlacement", oldValue, tabPlacement);
  333. revalidate();
  334. repaint();
  335. }
  336. }
  337. /**
  338. * Returns the policy used by the tabbedpane to layout the tabs when all the
  339. * tabs will not fit within a single run.
  340. * @see #setTabLayoutPolicy
  341. * @since 1.4
  342. */
  343. public int getTabLayoutPolicy() {
  344. return tabLayoutPolicy;
  345. }
  346. /**
  347. * Sets the policy which the tabbedpane will use in laying out the tabs
  348. * when all the tabs will not fit within a single run.
  349. * Possible values are:
  350. * <ul>
  351. * <li><code>JTabbedPane.WRAP_TAB_LAYOUT</code>
  352. * <li><code>JTabbedPane.SCROLL_TAB_LAYOUT</code>
  353. * </ul>
  354. *
  355. * The default value, if not set by the UI, is <code>JTabbedPane.WRAP_TAB_LAYOUT</code>.
  356. * <p>
  357. * Some look and feels might only support a subset of the possible
  358. * layout policies, in which case the value of this property may be
  359. * ignored.
  360. *
  361. * @param tabLayoutPolicy the policy used to layout the tabs
  362. * @exception IllegalArgumentException if layoutPolicy value isn't one
  363. * of the above valid values
  364. * @see #getTabLayoutPolicy
  365. * @since 1.4
  366. *
  367. * @beaninfo
  368. * preferred: true
  369. * bound: true
  370. * attribute: visualUpdate true
  371. * enum: WRAP_TAB_LAYOUT JTabbedPane.WRAP_TAB_LAYOUT
  372. * SCROLL_TAB_LAYOUT JTabbedPane.SCROLL_TAB_LAYOUT
  373. * description: The tabbedpane's policy for laying out the tabs
  374. *
  375. */
  376. public void setTabLayoutPolicy(int tabLayoutPolicy) {
  377. if (tabLayoutPolicy != WRAP_TAB_LAYOUT && tabLayoutPolicy != SCROLL_TAB_LAYOUT) {
  378. throw new IllegalArgumentException("illegal tab layout policy: must be WRAP_TAB_LAYOUT or SCROLL_TAB_LAYOUT");
  379. }
  380. if (this.tabLayoutPolicy != tabLayoutPolicy) {
  381. int oldValue = this.tabLayoutPolicy;
  382. this.tabLayoutPolicy = tabLayoutPolicy;
  383. firePropertyChange("tabLayoutPolicy", oldValue, tabLayoutPolicy);
  384. revalidate();
  385. repaint();
  386. }
  387. }
  388. /**
  389. * Returns the currently selected index for this tabbedpane.
  390. * Returns -1 if there is no currently selected tab.
  391. *
  392. * @return the index of the selected tab
  393. * @see #setSelectedIndex
  394. */
  395. public int getSelectedIndex() {
  396. return model.getSelectedIndex();
  397. }
  398. /**
  399. * Sets the selected index for this tabbedpane. The index must be
  400. * a valid tab index or -1, which indicates that no tab should be selected
  401. * (can also be used when there are no tabs in the tabbedpane). If a -1
  402. * value is specified when the tabbedpane contains one or more tabs, then
  403. * the results will be implementation defined.
  404. *
  405. * @param index the index to be selected
  406. * @exception IndexOutOfBoundsException if index is out of range
  407. * (index < -1 || index >= tab count)
  408. *
  409. * @see #getSelectedIndex
  410. * @see SingleSelectionModel#setSelectedIndex
  411. * @beaninfo
  412. * preferred: true
  413. * description: The tabbedpane's selected tab index.
  414. */
  415. public void setSelectedIndex(int index) {
  416. if (index != -1) {
  417. checkIndex(index);
  418. }
  419. setSelectedIndexImpl(index);
  420. }
  421. private void setSelectedIndexImpl(int index) {
  422. int oldIndex = model.getSelectedIndex();
  423. model.setSelectedIndex(index);
  424. if ((oldIndex >= 0) && (oldIndex != index)) {
  425. Page oldPage = (Page) pages.elementAt(oldIndex);
  426. oldPage.firePropertyChange(
  427. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  428. AccessibleState.SELECTED, null);
  429. }
  430. if ((index >= 0) && (oldIndex != index)) {
  431. Page newPage = (Page) pages.elementAt(index);
  432. newPage.firePropertyChange(
  433. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  434. null, AccessibleState.SELECTED);
  435. }
  436. }
  437. /**
  438. * Returns the currently selected component for this tabbedpane.
  439. * Returns <code>null</code> if there is no currently selected tab.
  440. *
  441. * @return the component corresponding to the selected tab
  442. * @see #setSelectedComponent
  443. */
  444. public Component getSelectedComponent() {
  445. int index = getSelectedIndex();
  446. if (index == -1) {
  447. return null;
  448. }
  449. return getComponentAt(index);
  450. }
  451. /**
  452. * Sets the selected component for this tabbedpane. This
  453. * will automatically set the <code>selectedIndex</code> to the index
  454. * corresponding to the specified component.
  455. *
  456. * @exception IllegalArgumentException if component not found in tabbed
  457. * pane
  458. * @see #getSelectedComponent
  459. * @beaninfo
  460. * preferred: true
  461. * description: The tabbedpane's selected component.
  462. */
  463. public void setSelectedComponent(Component c) {
  464. int index = indexOfComponent(c);
  465. if (index != -1) {
  466. setSelectedIndex(index);
  467. } else {
  468. throw new IllegalArgumentException("component not found in tabbed pane");
  469. }
  470. }
  471. /**
  472. * Inserts a <code>component</code>, at <code>index</code>,
  473. * represented by a <code>title</code> and/or <code>icon</code>,
  474. * either of which may be <code>null</code>. If <code>icon</code>
  475. * is non-<code>null</code> and it implements
  476. * <code>ImageIcon</code> a corresponding disabled icon will automatically
  477. * be created and set on the tabbedpane.
  478. * Uses java.util.Vector internally, see <code>insertElementAt</code>
  479. * for details of insertion conventions.
  480. *
  481. * @param title the title to be displayed in this tab
  482. * @param icon the icon to be displayed in this tab
  483. * @param component The component to be displayed when this tab is clicked.
  484. * @param tip the tooltip to be displayed for this tab
  485. * @param index the position to insert this new tab
  486. *
  487. * @see #addTab
  488. * @see #removeTabAt
  489. */
  490. public void insertTab(String title, Icon icon, Component component, String tip, int index) {
  491. int newIndex = index;
  492. Icon disabledIcon = null;
  493. if (icon != null && icon instanceof ImageIcon) {
  494. disabledIcon = new ImageIcon(
  495. GrayFilter.createDisabledImage(
  496. ((ImageIcon)icon).getImage()));
  497. }
  498. // If component already exists, remove corresponding
  499. // tab so that new tab gets added correctly
  500. // Note: we are allowing component=null because of compatibility,
  501. // but we really should throw an exception because much of the
  502. // rest of the JTabbedPane implementation isn't designed to deal
  503. // with null components for tabs.
  504. int removeIndex = indexOfComponent(component);
  505. if (component != null && removeIndex != -1) {
  506. removeTabAt(removeIndex);
  507. if (newIndex > removeIndex) {
  508. newIndex--;
  509. }
  510. }
  511. pages.insertElementAt(new Page(this, title != null? title : "", icon, disabledIcon,
  512. component, tip), newIndex);
  513. if (component != null) {
  514. component.setVisible(false);
  515. addImpl(component, null, -1);
  516. }
  517. if (pages.size() == 1) {
  518. setSelectedIndex(0);
  519. }
  520. if (!haveRegistered && tip != null) {
  521. ToolTipManager.sharedInstance().registerComponent(this);
  522. haveRegistered = true;
  523. }
  524. if (accessibleContext != null) {
  525. accessibleContext.firePropertyChange(
  526. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  527. null, component);
  528. }
  529. revalidate();
  530. repaint();
  531. }
  532. /**
  533. * Adds a <code>component</code> and <code>tip</code>
  534. * represented by a <code>title</code> and/or <code>icon</code>,
  535. * either of which can be <code>null</code>. If <code>icon</code>
  536. * is non-<code>null</code> and it implements <code>ImageIcon</code>
  537. * a corresponding disabled icon will automatically be created
  538. * and set on the tabbedpane.
  539. * Cover method for <code>insertTab</code>.
  540. *
  541. * @param title the title to be displayed in this tab
  542. * @param icon the icon to be displayed in this tab
  543. * @param component the component to be displayed when this tab is clicked
  544. * @param tip the tooltip to be displayed for this tab
  545. *
  546. * @see #insertTab
  547. * @see #removeTabAt
  548. */
  549. public void addTab(String title, Icon icon, Component component, String tip) {
  550. insertTab(title, icon, component, tip, pages.size());
  551. }
  552. /**
  553. * Adds a <code>component</code> represented by a <code>title</code>
  554. * and/or <code>icon</code>, either of which can be <code>null</code>.
  555. * If <code>icon</code> is non-<code>null</code> and it implements
  556. * <code>ImageIcon</code> a corresponding disabled icon will automatically
  557. * be created and set on the tabbedpane.
  558. * Cover method for <code>insertTab</code>.
  559. *
  560. * @param title the title to be displayed in this tab
  561. * @param icon the icon to be displayed in this tab
  562. * @param component the component to be displayed when this tab is clicked
  563. *
  564. * @see #insertTab
  565. * @see #removeTabAt
  566. */
  567. public void addTab(String title, Icon icon, Component component) {
  568. insertTab(title, icon, component, null, pages.size());
  569. }
  570. /**
  571. * Adds a <code>component</code> represented by a <code>title</code>
  572. * and no icon.
  573. * Cover method for <code>insertTab</code>.
  574. *
  575. * @param title the title to be displayed in this tab
  576. * @param component the component to be displayed when this tab is clicked
  577. *
  578. * @see #insertTab
  579. * @see #removeTabAt
  580. */
  581. public void addTab(String title, Component component) {
  582. insertTab(title, null, component, null, pages.size());
  583. }
  584. /**
  585. * Adds a <code>component</code> with a tab title defaulting to
  586. * the name of the component which is the result of calling
  587. * <code>component.getName</code>.
  588. * Cover method for <code>insertTab</code>.
  589. *
  590. * @param component the component to be displayed when this tab is clicked
  591. * @return the component
  592. *
  593. * @see #insertTab
  594. * @see #removeTabAt
  595. */
  596. public Component add(Component component) {
  597. if (!(component instanceof UIResource)) {
  598. addTab(component.getName(), component);
  599. } else {
  600. super.add(component);
  601. }
  602. return component;
  603. }
  604. /**
  605. * Adds a <code>component</code> with the specified tab title.
  606. * Cover method for <code>insertTab</code>.
  607. *
  608. * @param title the title to be displayed in this tab
  609. * @param component the component to be displayed when this tab is clicked
  610. * @return the component
  611. *
  612. * @see #insertTab
  613. * @see #removeTabAt
  614. */
  615. public Component add(String title, Component component) {
  616. if (!(component instanceof UIResource)) {
  617. addTab(title, component);
  618. } else {
  619. super.add(title, component);
  620. }
  621. return component;
  622. }
  623. /**
  624. * Adds a <code>component</code> at the specified tab index with a tab
  625. * title defaulting to the name of the component.
  626. * Cover method for <code>insertTab</code>.
  627. *
  628. * @param component the component to be displayed when this tab is clicked
  629. * @param index the position to insert this new tab
  630. * @return the component
  631. *
  632. * @see #insertTab
  633. * @see #removeTabAt
  634. */
  635. public Component add(Component component, int index) {
  636. if (!(component instanceof UIResource)) {
  637. // Container.add() interprets -1 as "append", so convert
  638. // the index appropriately to be handled by the vector
  639. insertTab(component.getName(), null, component, null,
  640. index == -1? getTabCount() : index);
  641. } else {
  642. super.add(component, index);
  643. }
  644. return component;
  645. }
  646. /**
  647. * Adds a <code>component</code> to the tabbed pane.
  648. * If <code>constraints</code> is a <code>String</code> or an
  649. * <code>Icon</code>, it will be used for the tab title,
  650. * otherwise the component's name will be used as the tab title.
  651. * Cover method for <code>insertTab</code>.
  652. *
  653. * @param component the component to be displayed when this tab is clicked
  654. * @param constraints the object to be displayed in the tab
  655. *
  656. * @see #insertTab
  657. * @see #removeTabAt
  658. */
  659. public void add(Component component, Object constraints) {
  660. if (!(component instanceof UIResource)) {
  661. if (constraints instanceof String) {
  662. addTab((String)constraints, component);
  663. } else if (constraints instanceof Icon) {
  664. addTab(null, (Icon)constraints, component);
  665. } else {
  666. add(component);
  667. }
  668. } else {
  669. super.add(component, constraints);
  670. }
  671. }
  672. /**
  673. * Adds a <code>component</code> at the specified tab index.
  674. * If <code>constraints</code> is a <code>String</code> or an
  675. * <code>Icon</code>, it will be used for the tab title,
  676. * otherwise the component's name will be used as the tab title.
  677. * Cover method for <code>insertTab</code>.
  678. *
  679. * @param component the component to be displayed when this tab is clicked
  680. * @param constraints the object to be displayed in the tab
  681. * @param index the position to insert this new tab
  682. *
  683. * @see #insertTab
  684. * @see #removeTabAt
  685. */
  686. public void add(Component component, Object constraints, int index) {
  687. if (!(component instanceof UIResource)) {
  688. Icon icon = constraints instanceof Icon? (Icon)constraints : null;
  689. String title = constraints instanceof String? (String)constraints : null;
  690. // Container.add() interprets -1 as "append", so convert
  691. // the index appropriately to be handled by the vector
  692. insertTab(title, icon, component, null, index == -1? getTabCount() : index);
  693. } else {
  694. super.add(component, constraints, index);
  695. }
  696. }
  697. /**
  698. * Removes the tab at <code>index</code>.
  699. * After the component associated with <code>index</code> is removed,
  700. * its visibility is reset to true to ensure it will be visible
  701. * if added to other containers.
  702. * @param index the index of the tab to be removed
  703. * @exception IndexOutOfBoundsException if index is out of range
  704. * (index < 0 || index >= tab count)
  705. *
  706. * @see #addTab
  707. * @see #insertTab
  708. */
  709. public void removeTabAt(int index) {
  710. checkIndex(index);
  711. // If we are removing the currently selected tab AND
  712. // it happens to be the last tab in the bunch, then
  713. // select the previous tab
  714. int tabCount = getTabCount();
  715. int selected = getSelectedIndex();
  716. if (selected >= (tabCount - 1)) {
  717. setSelectedIndexImpl(selected - 1);
  718. }
  719. Component component = getComponentAt(index);
  720. if (accessibleContext != null) {
  721. accessibleContext.firePropertyChange(
  722. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  723. component, null);
  724. }
  725. pages.removeElementAt(index);
  726. // NOTE 4/15/2002 (joutwate):
  727. // This fix is implemented using client properties since there is
  728. // currently no IndexPropertyChangeEvent. Once
  729. // IndexPropertyChangeEvents have been added this code should be
  730. // modified to use it.
  731. putClientProperty("__index_to_remove__", new Integer(index));
  732. // We can't assume the tab indices correspond to the
  733. // container's children array indices, so make sure we
  734. // remove the correct child!
  735. if (component != null) {
  736. Component components[] = getComponents();
  737. for (int i = components.length; --i >= 0; ) {
  738. if (components[i] == component) {
  739. super.remove(i);
  740. component.setVisible(true);
  741. break;
  742. }
  743. }
  744. }
  745. revalidate();
  746. repaint();
  747. }
  748. /**
  749. * Removes the specified <code>Component</code> from the
  750. * <code>JTabbedPane</code>.
  751. *
  752. * @param component the component to remove from the tabbedpane
  753. * @exception NullPointerException if <code>component</code> is null.
  754. * @see #addTab
  755. * @see #removeTabAt
  756. */
  757. public void remove(Component component) {
  758. int index = indexOfComponent(component);
  759. if (index != -1) {
  760. removeTabAt(index);
  761. } else {
  762. // Container#remove(comp) invokes Container#remove(int)
  763. // so make sure JTabbedPane#remove(int) isn't called here
  764. Component children[] = getComponents();
  765. for (int i=0; i < children.length; i++) {
  766. if (component == children[i]) {
  767. super.remove(i);
  768. break;
  769. }
  770. }
  771. }
  772. }
  773. /**
  774. * Removes the tab and component which corresponds to the specified index.
  775. *
  776. * @param index the index of the component to remove from the
  777. * <code>tabbedpane</code>
  778. * @exception IndexOutOfBoundsException if index is out of range
  779. * (index < 0 || index >= tab count)
  780. * @see #addTab
  781. * @see #removeTabAt
  782. */
  783. public void remove(int index) {
  784. removeTabAt(index);
  785. }
  786. /**
  787. * Removes all the tabs and their corresponding components
  788. * from the <code>tabbedpane</code>.
  789. *
  790. * @see #addTab
  791. * @see #removeTabAt
  792. */
  793. public void removeAll() {
  794. setSelectedIndexImpl(-1);
  795. int tabCount = getTabCount();
  796. // We invoke removeTabAt for each tab, otherwise we may end up
  797. // removing Components added by the UI.
  798. while (tabCount-- > 0) {
  799. removeTabAt(tabCount);
  800. }
  801. }
  802. /**
  803. * Returns the number of tabs in this <code>tabbedpane</code>.
  804. *
  805. * @return an integer specifying the number of tabbed pages
  806. */
  807. public int getTabCount() {
  808. return pages.size();
  809. }
  810. /**
  811. * Returns the number of tab runs currently used to display
  812. * the tabs.
  813. * @return an integer giving the number of rows if the
  814. * <code>tabPlacement</code>
  815. * is <code>TOP</code> or <code>BOTTOM</code>
  816. * and the number of columns if
  817. * <code>tabPlacement</code>
  818. * is <code>LEFT</code> or <code>RIGHT</code>,
  819. * or 0 if there is no UI set on this <code>tabbedpane</code>
  820. */
  821. public int getTabRunCount() {
  822. if (ui != null) {
  823. return ((TabbedPaneUI)ui).getTabRunCount(this);
  824. }
  825. return 0;
  826. }
  827. // Getters for the Pages
  828. /**
  829. * Returns the tab title at <code>index</code>.
  830. *
  831. * @param index the index of the item being queried
  832. * @return the title at <code>index</code>
  833. * @exception IndexOutOfBoundsException if index is out of range
  834. * (index < 0 || index >= tab count)
  835. * @see #setTitleAt
  836. */
  837. public String getTitleAt(int index) {
  838. return ((Page)pages.elementAt(index)).title;
  839. }
  840. /**
  841. * Returns the tab icon at <code>index</code>.
  842. *
  843. * @param index the index of the item being queried
  844. * @return the icon at <code>index</code>
  845. * @exception IndexOutOfBoundsException if index is out of range
  846. * (index < 0 || index >= tab count)
  847. *
  848. * @see #setIconAt * If <code>icon</code> is non-null and it implements <code>ImageIcon
  849. * </code> a corresponding disabled icon will automatically
  850. * be created and set on the tabbedpane.
  851. */
  852. public Icon getIconAt(int index) {
  853. return ((Page)pages.elementAt(index)).icon;
  854. }
  855. /**
  856. * Returns the tab disabled icon at <code>index</code>.
  857. *
  858. * @param index the index of the item being queried
  859. * @return the icon at <code>index</code>
  860. * @exception IndexOutOfBoundsException if index is out of range
  861. * (index < 0 || index >= tab count)
  862. *
  863. * @see #setDisabledIconAt
  864. */
  865. public Icon getDisabledIconAt(int index) {
  866. return ((Page)pages.elementAt(index)).disabledIcon;
  867. }
  868. /**
  869. * Returns the tab tooltip text at <code>index</code>.
  870. *
  871. * @param index the index of the item being queried
  872. * @return a string containing the tool tip text at <code>index</code>
  873. * @exception IndexOutOfBoundsException if index is out of range
  874. * (index < 0 || index >= tab count)
  875. *
  876. * @see #setToolTipTextAt
  877. */
  878. public String getToolTipTextAt(int index) {
  879. return ((Page)pages.elementAt(index)).tip;
  880. }
  881. /**
  882. * Returns the tab background color at <code>index</code>.
  883. *
  884. * @param index the index of the item being queried
  885. * @return the <code>Color</code> of the tab background at
  886. * <code>index</code>
  887. * @exception IndexOutOfBoundsException if index is out of range
  888. * (index < 0 || index >= tab count)
  889. *
  890. * @see #setBackgroundAt
  891. */
  892. public Color getBackgroundAt(int index) {
  893. return ((Page)pages.elementAt(index)).getBackground();
  894. }
  895. /**
  896. * Returns the tab foreground color at <code>index</code>.
  897. *
  898. * @param index the index of the item being queried
  899. * @return the <code>Color</code> of the tab foreground at
  900. * <code>index</code>
  901. * @exception IndexOutOfBoundsException if index is out of range
  902. * (index < 0 || index >= tab count)
  903. *
  904. * @see #setForegroundAt
  905. */
  906. public Color getForegroundAt(int index) {
  907. return ((Page)pages.elementAt(index)).getForeground();
  908. }
  909. /**
  910. * Returns whether or not the tab at <code>index</code> is
  911. * currently enabled.
  912. *
  913. * @param index the index of the item being queried
  914. * @return true if the tab at <code>index</code> is enabled;
  915. * false otherwise
  916. * @exception IndexOutOfBoundsException if index is out of range
  917. * (index < 0 || index >= tab count)
  918. *
  919. * @see #setEnabledAt
  920. */
  921. public boolean isEnabledAt(int index) {
  922. return ((Page)pages.elementAt(index)).isEnabled();
  923. }
  924. /**
  925. * Returns the component at <code>index</code>.
  926. *
  927. * @param index the index of the item being queried
  928. * @return the <code>Component</code> at <code>index</code>
  929. * @exception IndexOutOfBoundsException if index is out of range
  930. * (index < 0 || index >= tab count)
  931. *
  932. * @see #setComponentAt
  933. */
  934. public Component getComponentAt(int index) {
  935. return ((Page)pages.elementAt(index)).component;
  936. }
  937. /**
  938. * Returns the keyboard mnemonic for accessing the specified tab.
  939. * The mnemonic is the key which when combined with the look and feel's
  940. * mouseless modifier (usually Alt) will activate the specified
  941. * tab.
  942. *
  943. * @since 1.4
  944. * @param tabIndex the index of the tab that the mnemonic refers to
  945. * @return the key code which represents the mnemonic;
  946. * -1 if a mnemonic is not specified for the tab
  947. * @exception IndexOutOfBoundsException if index is out of range
  948. * (<code>tabIndex</code> < 0 ||
  949. * <code>tabIndex</code> >= tab count)
  950. * @see #setDisplayedMnemonicIndexAt(int,int)
  951. * @see #setMnemonicAt(int,int)
  952. */
  953. public int getMnemonicAt(int tabIndex) {
  954. checkIndex(tabIndex);
  955. Page page = (Page)pages.elementAt(tabIndex);
  956. return page.getMnemonic();
  957. }
  958. /**
  959. * Returns the character, as an index, that the look and feel should
  960. * provide decoration for as representing the mnemonic character.
  961. *
  962. * @since 1.4
  963. * @param tabIndex the index of the tab that the mnemonic refers to
  964. * @return index representing mnemonic character if one exists;
  965. * otherwise returns -1
  966. * @exception IndexOutOfBoundsException if index is out of range
  967. * (<code>tabIndex</code> < 0 ||
  968. * <code>tabIndex</code> >= tab count)
  969. * @see #setDisplayedMnemonicIndexAt(int,int)
  970. * @see #setMnemonicAt(int,int)
  971. */
  972. public int getDisplayedMnemonicIndexAt(int tabIndex) {
  973. checkIndex(tabIndex);
  974. Page page = (Page)pages.elementAt(tabIndex);
  975. return page.getDisplayedMnemonicIndex();
  976. }
  977. /**
  978. * Returns the tab bounds at <code>index</code>. If the tab at
  979. * this index is not currently visible in the UI, then returns
  980. * <code>null</code>.
  981. * If there is no UI set on this <code>tabbedpane</code>,
  982. * then returns <code>null</code>.
  983. *
  984. * @param index the index to be queried
  985. * @return a <code>Rectangle</code> containing the tab bounds at
  986. * <code>index</code>, or <code>null</code> if tab at
  987. * <code>index</code> is not currently visible in the UI,
  988. * or if there is no UI set on this <code>tabbedpane</code>
  989. * @exception IndexOutOfBoundsException if index is out of range
  990. * (index < 0 || index >= tab count)
  991. */
  992. public Rectangle getBoundsAt(int index) {
  993. checkIndex(index);
  994. if (ui != null) {
  995. return ((TabbedPaneUI)ui).getTabBounds(this, index);
  996. }
  997. return null;
  998. }
  999. // Setters for the Pages
  1000. /**
  1001. * Sets the title at <code>index</code> to <code>title</code> which
  1002. * can be <code>null</code>.
  1003. * An internal exception is raised if there is no tab at that index.
  1004. *
  1005. * @param index the tab index where the title should be set
  1006. * @param title the title to be displayed in the tab
  1007. * @exception IndexOutOfBoundsException if index is out of range
  1008. * (index < 0 || index >= tab count)
  1009. *
  1010. * @see #getTitleAt
  1011. * @beaninfo
  1012. * preferred: true
  1013. * attribute: visualUpdate true
  1014. * description: The title at the specified tab index.
  1015. */
  1016. public void setTitleAt(int index, String title) {
  1017. Page page = (Page)pages.elementAt(index);
  1018. String oldTitle =page.title;
  1019. page.title = title;
  1020. if (oldTitle != title) {
  1021. firePropertyChange("indexForTitle", -1, index);
  1022. }
  1023. page.updateDisplayedMnemonicIndex();
  1024. if ((oldTitle != title) && (accessibleContext != null)) {
  1025. accessibleContext.firePropertyChange(
  1026. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  1027. oldTitle, title);
  1028. }
  1029. if (title == null || oldTitle == null ||
  1030. !title.equals(oldTitle)) {
  1031. revalidate();
  1032. repaint();
  1033. }
  1034. }
  1035. /**
  1036. * Sets the icon at <code>index</code> to <code>icon</code> which can be
  1037. * <code>null</code>. Does not set disabled icon at <code>icon</code>
  1038. * To set disabled icon, use <code>setDisableIconAt()</code>.
  1039. * An internal exception is raised if there is no tab at that index.
  1040. *
  1041. * @param index the tab index where the icon should be set
  1042. * @param icon the icon to be displayed in the tab
  1043. * @exception IndexOutOfBoundsException if index is out of range
  1044. * (index < 0 || index >= tab count)
  1045. *
  1046. * @see #setDisabledIconAt
  1047. * @see #getIconAt
  1048. * @beaninfo
  1049. * preferred: true
  1050. * attribute: visualUpdate true
  1051. * description: The icon at the specified tab index.
  1052. */
  1053. public void setIconAt(int index, Icon icon) {
  1054. Icon oldIcon = ((Page)pages.elementAt(index)).icon;
  1055. ((Page)pages.elementAt(index)).icon = icon;
  1056. AccessibleContext ac = getAccessibleContext();
  1057. // Fire the accessibility Visible data change
  1058. if ((oldIcon != icon) && (accessibleContext != null)) {
  1059. accessibleContext.firePropertyChange(
  1060. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  1061. oldIcon, icon);
  1062. }
  1063. if (icon != oldIcon) {
  1064. revalidate();
  1065. repaint();
  1066. }
  1067. }
  1068. /**
  1069. * Sets the disabled icon at <code>index</code> to <code>icon</code>
  1070. * which can be <code>null</code>.
  1071. * An internal exception is raised if there is no tab at that index.
  1072. *
  1073. * @param index the tab index where the disabled icon should be set
  1074. * @param disabledIcon the icon to be displayed in the tab when disabled
  1075. * @exception IndexOutOfBoundsException if index is out of range
  1076. * (index < 0 || index >= tab count)
  1077. *
  1078. * @see #getDisabledIconAt
  1079. * @beaninfo
  1080. * preferred: true
  1081. * attribute: visualUpdate true
  1082. * description: The disabled icon at the specified tab index.
  1083. */
  1084. public void setDisabledIconAt(int index, Icon disabledIcon) {
  1085. Icon oldIcon = ((Page)pages.elementAt(index)).disabledIcon;
  1086. ((Page)pages.elementAt(index)).disabledIcon = disabledIcon;
  1087. if (disabledIcon != oldIcon && !isEnabledAt(index)) {
  1088. revalidate();
  1089. repaint();
  1090. }
  1091. }
  1092. /**
  1093. * Sets the tooltip text at <code>index</code> to <code>toolTipText</code>
  1094. * which can be <code>null</code>.
  1095. * An internal exception is raised if there is no tab at that index.
  1096. *
  1097. * @param index the tab index where the tooltip text should be set
  1098. * @param toolTipText the tooltip text to be displayed for the tab
  1099. * @exception IndexOutOfBoundsException if index is out of range
  1100. * (index < 0 || index >= tab count)
  1101. *
  1102. * @see #getToolTipTextAt
  1103. * @beaninfo
  1104. * preferred: true
  1105. * description: The tooltip text at the specified tab index.
  1106. */
  1107. public void setToolTipTextAt(int index, String toolTipText) {
  1108. String oldToolTipText =((Page)pages.elementAt(index)).tip;
  1109. ((Page)pages.elementAt(index)).tip = toolTipText;
  1110. if ((oldToolTipText != toolTipText) && (accessibleContext != null)) {
  1111. accessibleContext.firePropertyChange(
  1112. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  1113. oldToolTipText, toolTipText);
  1114. }
  1115. if (!haveRegistered && toolTipText != null) {
  1116. ToolTipManager.sharedInstance().registerComponent(this);
  1117. haveRegistered = true;
  1118. }
  1119. }
  1120. /**
  1121. * Sets the background color at <code>index</code> to
  1122. * <code>background</code>
  1123. * which can be <code>null</code>, in which case the tab's background color
  1124. * will default to the background color of the <code>tabbedpane</code>.
  1125. * An internal exception is raised if there is no tab at that index.
  1126. * @param index the tab index where the background should be set
  1127. * @param background the color to be displayed in the tab's background
  1128. * @exception IndexOutOfBoundsException if index is out of range
  1129. * (index < 0 || index >= tab count)
  1130. *
  1131. * @see #getBackgroundAt
  1132. * @beaninfo
  1133. * preferred: true
  1134. * attribute: visualUpdate true
  1135. * description: The background color at the specified tab index.
  1136. */
  1137. public void setBackgroundAt(int index, Color background) {
  1138. Color oldBg = ((Page)pages.elementAt(index)).background;
  1139. ((Page)pages.elementAt(index)).setBackground(background);
  1140. if (background == null || oldBg == null ||
  1141. !background.equals(oldBg)) {
  1142. Rectangle tabBounds = getBoundsAt(index);
  1143. if (tabBounds != null) {
  1144. repaint(tabBounds);
  1145. }
  1146. }
  1147. }
  1148. /**
  1149. * Sets the foreground color at <code>index</code> to
  1150. * <code>foreground</code> which can be
  1151. * <code>null</code>, in which case the tab's foreground color
  1152. * will default to the foreground color of this <code>tabbedpane</code>.
  1153. * An internal exception is raised if there is no tab at that index.
  1154. *
  1155. * @param index the tab index where the foreground should be set
  1156. * @param foreground the color to be displayed as the tab's foreground
  1157. * @exception IndexOutOfBoundsException if index is out of range
  1158. * (index < 0 || index >= tab count)
  1159. *
  1160. * @see #getForegroundAt
  1161. * @beaninfo
  1162. * preferred: true
  1163. * attribute: visualUpdate true
  1164. * description: The foreground color at the specified tab index.
  1165. */
  1166. public void setForegroundAt(int index, Color foreground) {
  1167. Color oldFg = ((Page)pages.elementAt(index)).foreground;
  1168. ((Page)pages.elementAt(index)).setForeground(foreground);
  1169. if (foreground == null || oldFg == null ||
  1170. !foreground.equals(oldFg)) {
  1171. Rectangle tabBounds = getBoundsAt(index);
  1172. if (tabBounds != null) {
  1173. repaint(tabBounds);
  1174. }
  1175. }
  1176. }
  1177. /**
  1178. * Sets whether or not the tab at <code>index</code> is enabled.
  1179. * An internal exception is raised if there is no tab at that index.
  1180. *
  1181. * @param index the tab index which should be enabled/disabled
  1182. * @param enabled whether or not the tab should be enabled
  1183. * @exception IndexOutOfBoundsException if index is out of range
  1184. * (index < 0 || index >= tab count)
  1185. *
  1186. * @see #isEnabledAt
  1187. */
  1188. public void setEnabledAt(int index, boolean enabled) {
  1189. boolean oldEnabled = ((Page)pages.elementAt(index)).isEnabled();
  1190. ((Page)pages.elementAt(index)).setEnabled(enabled);
  1191. if (enabled != oldEnabled) {
  1192. revalidate();
  1193. repaint();
  1194. }
  1195. }
  1196. /**
  1197. * Sets the component at <code>index</code> to <code>component</code>.
  1198. * An internal exception is raised if there is no tab at that index.
  1199. *
  1200. * @param index the tab index where this component is being placed
  1201. * @param component the component for the tab
  1202. * @exception IndexOutOfBoundsException if index is out of range
  1203. * (index < 0 || index >= tab count)
  1204. *
  1205. * @see #getComponentAt
  1206. * @beaninfo
  1207. * attribute: visualUpdate true
  1208. * description: The component at the specified tab index.
  1209. */
  1210. public void setComponentAt(int index, Component component) {
  1211. Page page = (Page)pages.elementAt(index);
  1212. if (component != page.component) {
  1213. if (page.component != null) {
  1214. // REMIND(aim): this is really silly;
  1215. // why not if (page.component.getParent() == this) remove(component)
  1216. synchronized(getTreeLock()) {
  1217. int count = getComponentCount();
  1218. Component children[] = getComponents();
  1219. for (int i = 0; i < count; i++) {
  1220. if (children[i] == page.component) {
  1221. super.remove(i);
  1222. }
  1223. }
  1224. }
  1225. }
  1226. page.component = component;
  1227. component.setVisible(getSelectedIndex() == index);
  1228. addImpl(component, null, -1);
  1229. revalidate();
  1230. }
  1231. }
  1232. /**
  1233. * Provides a hint to the look and feel as to which character in the
  1234. * text should be decorated to represent the mnemonic. Not all look and
  1235. * feels may support this. A value of -1 indicates either there is
  1236. * no mnemonic for this tab, or you do not wish the mnemonic to be
  1237. * displayed for this tab.
  1238. * <p>
  1239. * The value of this is updated as the properties relating to the
  1240. * mnemonic change (such as the mnemonic itself, the text...).
  1241. * You should only ever have to call this if
  1242. * you do not wish the default character to be underlined. For example, if
  1243. * the text at tab index 3 was 'Apple Price', with a mnemonic of 'p',
  1244. * and you wanted the 'P'
  1245. * to be decorated, as 'Apple <u>P</u>rice', you would have to invoke
  1246. * <code>setDisplayedMnemonicIndex(3, 6)</code> after invoking
  1247. * <code>setMnemonicAt(3, KeyEvent.VK_P)</code>.
  1248. * <p>Note that it is the programmer's responsibility to ensure
  1249. * that each tab has a unique mnemonic or unpredictable results may
  1250. * occur.
  1251. *
  1252. * @since 1.4
  1253. * @param tabIndex the index of the tab that the mnemonic refers to
  1254. * @param mnemonicIndex index into the <code>String</code> to underline
  1255. * @exception IndexOutOfBoundsException if <code>tabIndex</code> is
  1256. * out of range (<code>tabIndex < 0 || tabIndex >= tab
  1257. * count</code>)
  1258. * @exception IllegalArgumentException will be thrown if
  1259. * <code>mnemonicIndex</code> is >= length of the tab
  1260. * title , or < -1
  1261. * @see #setMnemonicAt(int,int)
  1262. * @see #getDisplayedMnemonicIndexAt(int)
  1263. *
  1264. * @beaninfo
  1265. * bound: true
  1266. * attribute: visualUpdate true
  1267. * description: the index into the String to draw the keyboard character
  1268. * mnemonic at
  1269. */
  1270. public void setDisplayedMnemonicIndexAt(int tabIndex, int mnemonicIndex) {
  1271. checkIndex(tabIndex);
  1272. Page page = (Page)pages.elementAt(tabIndex);
  1273. page.setDisplayedMnemonicIndex(mnemonicIndex);
  1274. }
  1275. /**
  1276. * Sets the keyboard mnemonic for accessing the specified tab.
  1277. * The mnemonic is the key which when combined with the look and feel's
  1278. * mouseless modifier (usually Alt) will activate the specified
  1279. * tab.
  1280. * <p>
  1281. * A mnemonic must correspond to a single key on the keyboard
  1282. * and should be specified using one of the <code>VK_XXX</code>
  1283. * keycodes defined in <code>java.awt.event.KeyEvent</code>.
  1284. * Mnemonics are case-insensitive, therefore a key event
  1285. * with the corresponding keycode would cause the button to be
  1286. * activated whether or not the Shift modifier was pressed.
  1287. * <p>
  1288. * This will update the displayed mnemonic property for the specified
  1289. * tab.
  1290. *
  1291. * @since 1.4
  1292. * @param tabIndex the index of the tab that the mnemonic refers to
  1293. * @param mnemonic the key code which represents the mnemonic
  1294. * @exception IndexOutOfBoundsException if <code>tabIndex</code> is out
  1295. * of range (<code>tabIndex < 0 || tabIndex >= tab count</code>)
  1296. * @see #getMnemonicAt(int)
  1297. * @see #setDisplayedMnemonicIndexAt(int,int)
  1298. *
  1299. * @beaninfo
  1300. * bound: true
  1301. * attribute: visualUpdate true
  1302. * description: The keyboard mnenmonic, as a KeyEvent VK constant,
  1303. * for the specified tab
  1304. */
  1305. public void setMnemonicAt(int tabIndex, int mnemonic) {
  1306. checkIndex(tabIndex);
  1307. Page page = (Page)pages.elementAt(tabIndex);
  1308. page.setMnemonic(mnemonic);
  1309. firePropertyChange("mnemonicAt", null, null);
  1310. }
  1311. // end of Page setters
  1312. /**
  1313. * Returns the first tab index with a given <code>title</code>, or
  1314. * -1 if no tab has this title.
  1315. *
  1316. * @param title the title for the tab
  1317. * @return the first tab index which matches <code>title</code>, or
  1318. * -1 if no tab has this title
  1319. */
  1320. public int indexOfTab(String title) {
  1321. for(int i = 0; i < getTabCount(); i++) {
  1322. if (getTitleAt(i).equals(title == null? "" : title)) {
  1323. return i;
  1324. }
  1325. }
  1326. return -1;
  1327. }
  1328. /**
  1329. * Returns the first tab index with a given <code>icon</code>,
  1330. * or -1 if no tab has this icon.
  1331. *
  1332. * @param icon the icon for the tab
  1333. * @return the first tab index which matches <code>icon</code>,
  1334. * or -1 if no tab has this icon
  1335. */
  1336. public int indexOfTab(Icon icon) {
  1337. for(int i = 0; i < getTabCount(); i++) {
  1338. Icon tabIcon = getIconAt(i);
  1339. if ((tabIcon != null && tabIcon.equals(icon)) ||
  1340. (tabIcon == null && tabIcon == icon)) {
  1341. return i;
  1342. }
  1343. }
  1344. return -1;
  1345. }
  1346. /**
  1347. * Returns the index of the tab for the specified component.
  1348. * Returns -1 if there is no tab for this component.
  1349. *
  1350. * @param component the component for the tab
  1351. * @return the first tab which matches this component, or -1
  1352. * if there is no tab for this component
  1353. */
  1354. public int indexOfComponent(Component component) {
  1355. for(int i = 0; i < getTabCount(); i++) {
  1356. Component c = getComponentAt(i);
  1357. if ((c != null && c.equals(component)) ||
  1358. (c == null && c == component)) {
  1359. return i;
  1360. }
  1361. }
  1362. return -1;
  1363. }
  1364. /**
  1365. * Returns the tab index corresponding to the tab whose bounds
  1366. * intersect the specified location. Returns -1 if no tab
  1367. * intersects the location.
  1368. *
  1369. * @param x the x location relative to this tabbedpane
  1370. * @param y the y location relative to this tabbedpane
  1371. * @return the tab index which intersects the location, or
  1372. * -1 if no tab intersects the location
  1373. * @since 1.4
  1374. */
  1375. public int indexAtLocation(int x, int y) {
  1376. if (ui != null) {
  1377. return ((TabbedPaneUI)ui).tabForCoordinate(this, x, y);
  1378. }
  1379. return -1;
  1380. }
  1381. /**
  1382. * Returns the tooltip text for the component determined by the
  1383. * mouse event location.
  1384. *
  1385. * @param event the <code>MouseEvent</code> that tells where the
  1386. * cursor is lingering
  1387. * @return the <code>String</code> containing the tooltip text
  1388. */
  1389. public String getToolTipText(MouseEvent event) {
  1390. if (ui != null) {
  1391. int index = ((TabbedPaneUI)ui).tabForCoordinate(this, event.getX(), event.getY());
  1392. if (index != -1) {
  1393. return ((Page)pages.elementAt(index)).tip;
  1394. }
  1395. }
  1396. return super.getToolTipText(event);
  1397. }
  1398. private void checkIndex(int index) {
  1399. if (index < 0 || index >= pages.size()) {
  1400. throw new IndexOutOfBoundsException("Index: "+index+", Tab count: "+pages.size());
  1401. }
  1402. }
  1403. /**
  1404. * See <code>readObject</code> and <code>writeObject</code> in
  1405. * <code>JComponent</code> for more
  1406. * information about serialization in Swing.
  1407. */
  1408. private void writeObject(ObjectOutputStream s) throws IOException {
  1409. s.defaultWriteObject();
  1410. if (getUIClassID().equals(uiClassID)) {
  1411. byte count = JComponent.getWriteObjCounter(this);
  1412. JComponent.setWriteObjCounter(this, --count);
  1413. if (count == 0 && ui != null) {
  1414. ui.installUI(this);
  1415. }
  1416. }
  1417. }
  1418. /* Called from the <code>JComponent</code>'s
  1419. * <code>EnableSerializationFocusListener</code> to
  1420. * do any Swing-specific pre-serialization configuration.
  1421. */
  1422. void compWriteObjectNotify() {
  1423. super.compWriteObjectNotify();
  1424. // If ToolTipText != null, then the tooltip has already been
  1425. // unregistered by JComponent.compWriteObjectNotify()
  1426. if (getToolTipText() == null && haveRegistered) {
  1427. ToolTipManager.sharedInstance().unregisterComponent(this);
  1428. }
  1429. }
  1430. /**
  1431. * See <code>readObject</code> and <code>writeObject</code> in
  1432. * <code>JComponent</code> for more
  1433. * information about serialization in Swing.
  1434. */
  1435. private void readObject(ObjectInputStream s)
  1436. throws IOException, ClassNotFoundException
  1437. {
  1438. s.defaultReadObject();
  1439. if ((ui != null) && (getUIClassID().equals(uiClassID))) {
  1440. ui.installUI(this);
  1441. }
  1442. // If ToolTipText != null, then the tooltip has already been
  1443. // registered by JComponent.readObject()
  1444. if (getToolTipText() == null && haveRegistered) {
  1445. ToolTipManager.sharedInstance().registerComponent(this);
  1446. }
  1447. }
  1448. /**
  1449. * Returns a string representation of this <code>JTabbedPane</code>.
  1450. * This method
  1451. * is intended to be used only for debugging purposes, and the
  1452. * content and format of the returned string may vary between
  1453. * implementations. The returned string may be empty but may not
  1454. * be <code>null</code>.
  1455. *
  1456. * @return a string representation of this JTabbedPane.
  1457. */
  1458. protected String paramString() {
  1459. String tabPlacementString;
  1460. if (tabPlacement == TOP) {
  1461. tabPlacementString = "TOP";
  1462. } else if (tabPlacement == BOTTOM) {
  1463. tabPlacementString = "BOTTOM";
  1464. } else if (tabPlacement == LEFT) {
  1465. tabPlacementString = "LEFT";
  1466. } else if (tabPlacement == RIGHT) {
  1467. tabPlacementString = "RIGHT";
  1468. } else tabPlacementString = "";
  1469. String haveRegisteredString = (haveRegistered ?
  1470. "true" : "false");
  1471. return super.paramString() +
  1472. ",haveRegistered=" + haveRegisteredString +
  1473. ",tabPlacement=" + tabPlacementString;
  1474. }
  1475. /////////////////
  1476. // Accessibility support
  1477. ////////////////
  1478. /**
  1479. * Gets the AccessibleContext associated with this JTabbedPane.
  1480. * For tabbed panes, the AccessibleContext takes the form of an
  1481. * AccessibleJTabbedPane.
  1482. * A new AccessibleJTabbedPane instance is created if necessary.
  1483. *
  1484. * @return an AccessibleJTabbedPane that serves as the
  1485. * AccessibleContext of this JTabbedPane
  1486. */
  1487. public AccessibleContext getAccessibleContext() {
  1488. if (accessibleContext == null) {
  1489. accessibleContext = new AccessibleJTabbedPane();
  1490. }
  1491. return accessibleContext;
  1492. }
  1493. /**
  1494. * This class implements accessibility support for the
  1495. * <code>JTabbedPane</code> class. It provides an implementation of the
  1496. * Java Accessibility API appropriate to tabbed pane user-interface
  1497. * elements.
  1498. * <p>
  1499. * <strong>Warning:</strong>
  1500. * Serialized objects of this class will not be compatible with
  1501. * future Swing releases. The current serialization support is
  1502. * appropriate for short term storage or RMI between applications running
  1503. * the same version of Swing. As of 1.4, support for long term storage
  1504. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  1505. * has been added to the <code>java.beans</code> package.
  1506. * Please see {@link java.beans.XMLEncoder}.
  1507. */
  1508. protected class AccessibleJTabbedPane extends AccessibleJComponent
  1509. implements AccessibleSelection, ChangeListener {
  1510. /**
  1511. * Constructs an AccessibleJTabbedPane
  1512. */
  1513. public AccessibleJTabbedPane() {
  1514. super();
  1515. JTabbedPane.this.model.addChangeListener(this);
  1516. }
  1517. public void stateChanged(ChangeEvent e) {
  1518. Object o = e.getSource();
  1519. firePropertyChange(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY,
  1520. null, o);
  1521. }
  1522. /**
  1523. * Get the role of this object.
  1524. *
  1525. * @return an instance of AccessibleRole describing the role of
  1526. * the object
  1527. */
  1528. public AccessibleRole getAccessibleRole() {
  1529. return AccessibleRole.PAGE_TAB_LIST;
  1530. }
  1531. /**
  1532. * Returns the number of accessible children in the object.
  1533. *
  1534. * @return the number of accessible children in the object.
  1535. */
  1536. public int getAccessibleChildrenCount() {
  1537. return getTabCount();
  1538. }
  1539. /**
  1540. * Return the specified Accessible child of the object.
  1541. *
  1542. * @param i zero-based index of child
  1543. * @return the Accessible child of the object
  1544. * @exception IllegalArgumentException if index is out of bounds
  1545. */
  1546. public Accessible getAccessibleChild(int i) {
  1547. if (i < 0 || i >= getTabCount()) {
  1548. return null;
  1549. }
  1550. return (Accessible) pages.elementAt(i);
  1551. }
  1552. /**
  1553. * Gets the <code>AccessibleSelection</code> associated with
  1554. * this object. In the implementation of the Java
  1555. * Accessibility API for this class,
  1556. * returns this object, which is responsible for implementing the
  1557. * <code>AccessibleSelection</code> interface on behalf of itself.
  1558. *
  1559. * @return this object
  1560. */
  1561. public AccessibleSelection getAccessibleSelection() {
  1562. return this;
  1563. }
  1564. /**
  1565. * Returns the <code>Accessible</code> child contained at
  1566. * the local coordinate <code>Point</code>, if one exists.
  1567. * Otherwise returns the currently selected tab.
  1568. *
  1569. * @return the <code>Accessible</code> at the specified
  1570. * location, if it exists
  1571. */
  1572. public Accessible getAccessibleAt(Point p) {
  1573. int tab = ((TabbedPaneUI) ui).tabForCoordinate(JTabbedPane.this,
  1574. p.x, p.y);
  1575. if (tab == -1) {
  1576. tab = getSelectedIndex();
  1577. }
  1578. return getAccessibleChild(tab);
  1579. }
  1580. public int getAccessibleSelectionCount() {
  1581. return 1;
  1582. }
  1583. public Accessible getAccessibleSelection(int i) {
  1584. int index = getSelectedIndex();
  1585. if (index == -1) {
  1586. return null;
  1587. }
  1588. return (Accessible) pages.elementAt(index);
  1589. }
  1590. public boolean isAccessibleChildSelected(int i) {
  1591. return (i == getSelectedIndex());
  1592. }
  1593. public void addAccessibleSelection(int i) {
  1594. setSelectedIndex(i);
  1595. }
  1596. public void removeAccessibleSelection(int i) {
  1597. // can't do
  1598. }
  1599. public void clearAccessibleSelection() {
  1600. // can't do
  1601. }
  1602. public void selectAllAccessibleSelection() {
  1603. // can't do
  1604. }
  1605. }
  1606. private class Page extends AccessibleContext
  1607. implements Serializable, Accessible, AccessibleComponent {
  1608. String title;
  1609. Color background;
  1610. Color foreground;
  1611. Icon icon;
  1612. Icon disabledIcon;
  1613. JTabbedPane parent;
  1614. Component component;
  1615. String tip;
  1616. boolean enabled = true;
  1617. boolean needsUIUpdate;
  1618. int mnemonic;
  1619. int mnemonicIndex = -1;
  1620. Page(JTabbedPane parent,
  1621. String title, Icon icon, Icon disabledIcon, Component component, String tip) {
  1622. this.title = title;
  1623. this.icon = icon;
  1624. this.disabledIcon = disabledIcon;
  1625. this.parent = parent;
  1626. this.setAccessibleParent(parent);
  1627. this.component = component;
  1628. this.tip = tip;
  1629. if (component instanceof Accessible) {
  1630. AccessibleContext ac;
  1631. ac = ((Accessible) component).getAccessibleContext();
  1632. if (ac != null) {
  1633. ac.setAccessibleParent(this);
  1634. }
  1635. }
  1636. }
  1637. void setMnemonic(int mnemonic) {
  1638. this.mnemonic = mnemonic;
  1639. updateDisplayedMnemonicIndex();
  1640. }
  1641. int getMnemonic() {
  1642. return mnemonic;
  1643. }
  1644. /*
  1645. * Sets the page displayed mnemonic index
  1646. */
  1647. void setDisplayedMnemonicIndex(int mnemonicIndex) {
  1648. if (this.mnemonicIndex != mnemonicIndex) {
  1649. if (mnemonicIndex != -1 && (title == null ||
  1650. mnemonicIndex < 0 ||
  1651. mnemonicIndex >= title.length())) {
  1652. throw new IllegalArgumentException(
  1653. "Invalid mnemonic index: " + mnemonicIndex);
  1654. }
  1655. this.mnemonicIndex = mnemonicIndex;
  1656. JTabbedPane.this.firePropertyChange("displayedMnemonicIndexAt",
  1657. null, null);
  1658. }
  1659. }
  1660. /*
  1661. * Returns the page displayed mnemonic index
  1662. */
  1663. int getDisplayedMnemonicIndex() {
  1664. return this.mnemonicIndex;
  1665. }
  1666. void updateDisplayedMnemonicIndex() {
  1667. setDisplayedMnemonicIndex(
  1668. SwingUtilities.findDisplayedMnemonicIndex(title, mnemonic));
  1669. }
  1670. /////////////////
  1671. // Accessibility support
  1672. ////////////////
  1673. public AccessibleContext getAccessibleContext() {
  1674. return this;
  1675. }
  1676. // AccessibleContext methods
  1677. public String getAccessibleName() {
  1678. if (accessibleName != null) {
  1679. return accessibleName;
  1680. } else if (title != null) {
  1681. return title;
  1682. }
  1683. return null;
  1684. }
  1685. public String getAccessibleDescription() {
  1686. if (accessibleDescription != null) {
  1687. return accessibleDescription;
  1688. } else if (tip != null) {
  1689. return tip;
  1690. }
  1691. return null;
  1692. }
  1693. public AccessibleRole getAccessibleRole() {
  1694. return AccessibleRole.PAGE_TAB;
  1695. }
  1696. public AccessibleStateSet getAccessibleStateSet() {
  1697. AccessibleStateSet states;
  1698. states = parent.getAccessibleContext().getAccessibleStateSet();
  1699. states.add(AccessibleState.SELECTABLE);
  1700. int i = parent.indexOfTab(title);
  1701. if (i == parent.getSelectedIndex()) {
  1702. states.add(AccessibleState.SELECTED);
  1703. }
  1704. return states;
  1705. }
  1706. public int getAccessibleIndexInParent() {
  1707. return parent.indexOfTab(title);
  1708. }
  1709. public int getAccessibleChildrenCount() {
  1710. if (component instanceof Accessible) {
  1711. return 1;
  1712. } else {
  1713. return 0;
  1714. }
  1715. }
  1716. public Accessible getAccessibleChild(int i) {
  1717. if (component instanceof Accessible) {
  1718. return (Accessible) component;
  1719. } else {
  1720. return null;
  1721. }
  1722. }
  1723. public Locale getLocale() {
  1724. return parent.getLocale();
  1725. }
  1726. public AccessibleComponent getAccessibleComponent() {
  1727. return this;
  1728. }
  1729. // AccessibleComponent methods
  1730. public Color getBackground() {
  1731. return background != null? background : parent.getBackground();
  1732. }
  1733. public void setBackground(Color c) {
  1734. background = c;
  1735. }
  1736. public Color getForeground() {
  1737. return foreground != null? foreground : parent.getForeground();
  1738. }
  1739. public void setForeground(Color c) {
  1740. foreground = c;
  1741. }
  1742. public Cursor getCursor() {
  1743. return parent.getCursor();
  1744. }
  1745. public void setCursor(Cursor c) {
  1746. parent.setCursor(c);
  1747. }
  1748. public Font getFont() {
  1749. return parent.getFont();
  1750. }
  1751. public void setFont(Font f) {
  1752. parent.setFont(f);
  1753. }
  1754. public FontMetrics getFontMetrics(Font f) {
  1755. return parent.getFontMetrics(f);
  1756. }
  1757. public boolean isEnabled() {
  1758. return enabled;
  1759. }
  1760. public void setEnabled(boolean b) {
  1761. enabled = b;
  1762. }
  1763. public boolean isVisible() {
  1764. return parent.isVisible();
  1765. }
  1766. public void setVisible(boolean b) {
  1767. parent.setVisible(b);
  1768. }
  1769. public boolean isShowing() {
  1770. return parent.isShowing();
  1771. }
  1772. public boolean contains(Point p) {
  1773. Rectangle r = getBounds();
  1774. return r.contains(p);
  1775. }
  1776. public Point getLocationOnScreen() {
  1777. Point parentLocation = parent.getLocationOnScreen();
  1778. Point componentLocation = getLocation();
  1779. componentLocation.translate(parentLocation.x, parentLocation.y);
  1780. return componentLocation;
  1781. }
  1782. public Point getLocation() {
  1783. Rectangle r = getBounds();
  1784. return new Point(r.x, r.y);
  1785. }
  1786. public void setLocation(Point p) {
  1787. // do nothing
  1788. }
  1789. public Rectangle getBounds() {
  1790. return parent.getUI().getTabBounds(parent,
  1791. parent.indexOfTab(title));
  1792. }
  1793. public void setBounds(Rectangle r) {
  1794. // do nothing
  1795. }
  1796. public Dimension getSize() {
  1797. Rectangle r = getBounds();
  1798. return new Dimension(r.width, r.height);
  1799. }
  1800. public void setSize(Dimension d) {
  1801. // do nothing
  1802. }
  1803. public Accessible getAccessibleAt(Point p) {
  1804. if (component instanceof Accessible) {
  1805. return (Accessible) component;
  1806. } else {
  1807. return null;
  1808. }
  1809. }
  1810. public boolean isFocusTraversable() {
  1811. return false;
  1812. }
  1813. public void requestFocus() {
  1814. // do nothing
  1815. }
  1816. public void addFocusListener(FocusListener l) {
  1817. // do nothing
  1818. }
  1819. public void removeFocusListener(FocusListener l) {
  1820. // do nothing
  1821. }
  1822. }
  1823. }