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