1. /*
  2. * %W% %E%
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  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 which lets the user switch between a group of components by
  21. * clicking on a tab with a given title and/or icon.
  22. * <p>
  23. * Tabs/components are added to a TabbedPane object by using the addTab and
  24. * insertTab methods. A tab is represented by an index corresponding
  25. * to the position it was added in, where the first tab has an index equal to 0
  26. * and the last tab has an index equal to the tab count minus 1.
  27. * <p>
  28. * The TabbedPane uses a SingleSelectionModel to represent the set
  29. * of tab indeces and the currently selected index. If the tab count
  30. * is greater than 0, then there will always be a selected index, which
  31. * by default will be initialized to the first tab. If the tab count is
  32. * 0, then the selected index will be -1.
  33. * <p>
  34. * See <a href="http://java.sun.com/docs/books/tutorial/ui/swing/tabbedpane.html">How to Use Tabbed Panes</a>
  35. * in <a href="http://java.sun.com/Series/Tutorial/index.html"><em>The Java Tutorial</em></a>
  36. * for further documentation.
  37. * <p>
  38. * For the keyboard keys used by this component in the standard Look and
  39. * Feel (L&F) renditions, see the
  40. * <a href="doc-files/Key-Index.html#JTabbedPane">JTabbedPane</a> key assignments.
  41. * <p>
  42. * <strong>Warning:</strong>
  43. * Serialized objects of this class will not be compatible with
  44. * future Swing releases. The current serialization support is appropriate
  45. * for short term storage or RMI between applications running the same
  46. * version of Swing. A future release of Swing will provide support for
  47. * long term persistence.
  48. *
  49. * @beaninfo
  50. * attribute: isContainer true
  51. * description: A component which provides a tab folder metaphor for
  52. * displaying one component from a set of components.
  53. *
  54. * @version %I% %G%
  55. * @author Dave Moore
  56. * @author Philip Milne
  57. * @author Amy Fowler
  58. *
  59. * @see SingleSelectionModel
  60. */
  61. public class JTabbedPane extends JComponent
  62. implements Serializable, Accessible, SwingConstants {
  63. /**
  64. * @see #getUIClassID
  65. * @see #readObject
  66. */
  67. private static final String uiClassID = "TabbedPaneUI";
  68. /**
  69. * Where the tabs are placed.
  70. * @see #setTabPlacement
  71. */
  72. protected int tabPlacement = TOP;
  73. /** The default selection model */
  74. protected SingleSelectionModel model;
  75. private boolean haveRegistered;
  76. /**
  77. * The changeListener is the listener we add to the
  78. * model.
  79. */
  80. protected ChangeListener changeListener = null;
  81. Vector pages;
  82. /**
  83. * Only one ChangeEvent is needed per TabPane instance since the
  84. * event's only (read-only) state is the source property. The source
  85. * of events generated here is always "this".
  86. */
  87. protected transient ChangeEvent changeEvent = null;
  88. /**
  89. * Creates an empty TabbedPane.
  90. * @see #addTab
  91. */
  92. public JTabbedPane() {
  93. this(TOP);
  94. }
  95. /**
  96. * Creates an empty TabbedPane with the specified tab placement
  97. * of either: TOP, BOTTOM, LEFT, or RIGHT.
  98. * @param tabPlacement the placement for the tabs relative to the content
  99. * @see #addTab
  100. */
  101. public JTabbedPane(int tabPlacement) {
  102. setTabPlacement(tabPlacement);
  103. pages = new Vector(1);
  104. setModel(new DefaultSingleSelectionModel());
  105. updateUI();
  106. }
  107. /**
  108. * Returns the UI object which implements the L&F for this component.
  109. * @see #setUI
  110. */
  111. public TabbedPaneUI getUI() {
  112. return (TabbedPaneUI)ui;
  113. }
  114. /**
  115. * Sets the UI object which implements the L&F for this component.
  116. * @param ui the new UI object
  117. * @see UIDefaults#getUI
  118. * @beaninfo
  119. * bound: true
  120. * hidden: true
  121. * attribute: visualUpdate true
  122. * description: The UI object that implements the tabbedpane's LookAndFeel
  123. */
  124. public void setUI(TabbedPaneUI ui) {
  125. super.setUI(ui);
  126. }
  127. /**
  128. * Notification from the UIManager that the L&F has changed.
  129. * Called to replace the UI with the latest version from the
  130. * default UIFactory.
  131. *
  132. * @see JComponent#updateUI
  133. */
  134. public void updateUI() {
  135. setUI((TabbedPaneUI)UIManager.getUI(this));
  136. }
  137. /**
  138. * Returns the name of the UI class that implements the
  139. * L&F for this component.
  140. *
  141. * @return "TabbedPaneUI"
  142. * @see JComponent#getUIClassID
  143. * @see UIDefaults#getUI
  144. */
  145. public String getUIClassID() {
  146. return uiClassID;
  147. }
  148. /**
  149. * We pass ModelChanged events along to the listeners with
  150. * the tabbedpane (instead of the model itself) as the event source.
  151. */
  152. protected class ModelListener implements ChangeListener, Serializable {
  153. public void stateChanged(ChangeEvent e) {
  154. fireStateChanged();
  155. }
  156. }
  157. /**
  158. * Subclasses that want to handle ChangeEvents differently
  159. * can override this to return a subclass of ModelListener or
  160. * another ChangeListener implementation.
  161. *
  162. * @see #fireStateChanged
  163. */
  164. protected ChangeListener createChangeListener() {
  165. return new ModelListener();
  166. }
  167. /**
  168. * Adds a ChangeListener to this tabbedpane.
  169. *
  170. * @param l the ChangeListener to add
  171. * @see #fireStateChanged
  172. * @see #removeChangeListener
  173. */
  174. public void addChangeListener(ChangeListener l) {
  175. listenerList.add(ChangeListener.class, l);
  176. }
  177. /**
  178. * Removes a ChangeListener from this tabbedpane.
  179. *
  180. * @param l the ChangeListener to remove
  181. * @see #fireStateChanged
  182. * @see #addChangeListener
  183. */
  184. public void removeChangeListener(ChangeListener l) {
  185. listenerList.remove(ChangeListener.class, l);
  186. }
  187. /**
  188. * Send a ChangeEvent, whose source is this tabbedpane, to
  189. * each listener. This method method is called each time
  190. * a ChangeEvent is received from the model.
  191. *
  192. * @see #addChangeListener
  193. * @see EventListenerList
  194. */
  195. protected void fireStateChanged() {
  196. // Guaranteed to return a non-null array
  197. Object[] listeners = listenerList.getListenerList();
  198. // Process the listeners last to first, notifying
  199. // those that are interested in this event
  200. for (int i = listeners.length-2; i>=0; i-=2) {
  201. if (listeners[i]==ChangeListener.class) {
  202. // Lazily create the event:
  203. if (changeEvent == null)
  204. changeEvent = new ChangeEvent(this);
  205. ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
  206. }
  207. }
  208. }
  209. /**
  210. * Returns the model associated with this tabbedpane.
  211. *
  212. * @see #setModel
  213. */
  214. public SingleSelectionModel getModel() {
  215. return model;
  216. }
  217. /**
  218. * Sets the model to be used with this tabbedpane.
  219. * @param model the model to be used
  220. *
  221. * @see #getModel
  222. * @beaninfo
  223. * bound: true
  224. * description: The tabbedpane's SingleSelectionModel.
  225. */
  226. public void setModel(SingleSelectionModel model) {
  227. SingleSelectionModel oldModel = getModel();
  228. if (oldModel != null) {
  229. oldModel.removeChangeListener(changeListener);
  230. changeListener = null;
  231. }
  232. this.model = model;
  233. if (model != null) {
  234. changeListener = createChangeListener();
  235. model.addChangeListener(changeListener);
  236. }
  237. firePropertyChange("model", oldModel, model);
  238. repaint();
  239. }
  240. /**
  241. * Returns the placement of the tabs for this tabbedpane.
  242. * @see #setTabPlacement
  243. */
  244. public int getTabPlacement() {
  245. return tabPlacement;
  246. }
  247. /**
  248. * Sets the tab placement for this tabbedpane.
  249. * Possible values are:<ul>
  250. * <li>SwingConstants.TOP
  251. * <li>SwingConstants.BOTTOM
  252. * <li>SwingConstants.LEFT
  253. * <li>SwingConstants.RIGHT
  254. * </ul>
  255. * The default value, if not set, is TOP.
  256. *
  257. * @param tabPlacement the placement for the tabs relative to the content
  258. *
  259. * @beaninfo
  260. * preferred: true
  261. * bound: true
  262. * attribute: visualUpdate true
  263. * enum: TOP JTabbedPane.TOP
  264. * LEFT JTabbedPane.LEFT
  265. * BOTTOM JTabbedPane.BOTTOM
  266. * RIGHT JTabbedPane.RIGHT
  267. * description: The tabbedpane's tab placement.
  268. *
  269. */
  270. public void setTabPlacement(int tabPlacement) {
  271. if (tabPlacement != TOP && tabPlacement != LEFT &&
  272. tabPlacement != BOTTOM && tabPlacement != RIGHT) {
  273. throw new IllegalArgumentException("illegal tab placement: must be TOP, BOTTOM, LEFT, or RIGHT");
  274. }
  275. if (this.tabPlacement != tabPlacement) {
  276. int oldValue = this.tabPlacement;
  277. this.tabPlacement = tabPlacement;
  278. firePropertyChange("tabPlacement", oldValue, tabPlacement);
  279. revalidate();
  280. repaint();
  281. }
  282. }
  283. /**
  284. * Returns the currently selected index for this tabbedpane.
  285. * Returns -1 if there is no currently selected tab.
  286. *
  287. * @return the index of the selected tab
  288. * @see #setSelectedIndex
  289. */
  290. public int getSelectedIndex() {
  291. return model.getSelectedIndex();
  292. }
  293. /**
  294. * Sets the selected index for this tabbedpane.
  295. *
  296. * @see #getSelectedIndex
  297. * @see SingleSelectionModel#setSelectedIndex
  298. * @beaninfo
  299. * preferred: true
  300. * description: The tabbedpane's selected tab index.
  301. */
  302. public void setSelectedIndex(int index) {
  303. int oldIndex = model.getSelectedIndex();
  304. model.setSelectedIndex(index);
  305. if ((oldIndex >= 0) && (oldIndex != index)) {
  306. Page oldPage = (Page) pages.elementAt(oldIndex);
  307. if (accessibleContext != null) {
  308. accessibleContext.firePropertyChange(
  309. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  310. AccessibleState.SELECTED, null);
  311. }
  312. }
  313. if ((index >= 0) && (oldIndex != index)) {
  314. Page newPage = (Page) pages.elementAt(index);
  315. if (accessibleContext != null) {
  316. accessibleContext.firePropertyChange(
  317. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  318. null, AccessibleState.SELECTED);
  319. }
  320. }
  321. }
  322. /**
  323. * Returns the currently selected component for this tabbedpane.
  324. * Returns null if there is no currently selected tab.
  325. *
  326. * @return the component corresponding to the selected tab
  327. * @see #setSelectedComponent
  328. */
  329. public Component getSelectedComponent() {
  330. int index = getSelectedIndex();
  331. if (index == -1) {
  332. return null;
  333. }
  334. return getComponentAt(index);
  335. }
  336. /**
  337. * Sets the selected component for this tabbedpane. This
  338. * will automatically set the selectedIndex to the index
  339. * corresponding to the specified component.
  340. *
  341. * @see #getSelectedComponent
  342. * @beaninfo
  343. * preferred: true
  344. * description: The tabbedpane's selected component.
  345. */
  346. public void setSelectedComponent(Component c) {
  347. int index = indexOfComponent(c);
  348. if (index != -1) {
  349. setSelectedIndex(index);
  350. } else {
  351. throw new IllegalArgumentException("component not found in tabbed pane");
  352. }
  353. }
  354. /**
  355. * Inserts a <i>component</i>, at <i>index</i>, represented by a
  356. * <i>title</i> and/or <i>icon</i>, either of which may be null.
  357. * Uses java.util.Vector internally, see insertElementAt()
  358. * for details of insertion conventions.
  359. * @param title the title to be displayed in this tab
  360. * @param icon the icon to be displayed in this tab
  361. * @param component The component to be displayed when this tab is clicked.
  362. * @param tip the tooltip to be displayed for this tab
  363. * @param index the position to insert this new tab
  364. *
  365. * @see #addTab
  366. * @see #removeTabAt
  367. */
  368. public void insertTab(String title, Icon icon, Component component, String tip, int index) {
  369. Icon disabledIcon = null;
  370. if (icon != null && icon instanceof ImageIcon) {
  371. disabledIcon = new ImageIcon(
  372. GrayFilter.createDisabledImage(
  373. ((ImageIcon)icon).getImage()));
  374. }
  375. // If component already exists, remove corresponding
  376. // tab so that new tab gets added correctly
  377. // Note: we are allowing component=null because of compatibility,
  378. // but we really should throw an exception because much of the
  379. // rest of the JTabbedPane implementation isn't designed to deal
  380. // with null components for tabs.
  381. int i;
  382. if (component != null && (i = indexOfComponent(component)) != -1) {
  383. removeTabAt(i);
  384. }
  385. pages.insertElementAt(new Page(this, title != null? title : "", icon, disabledIcon,
  386. component, tip), index);
  387. if (component != null) {
  388. component.setVisible(false);
  389. addImpl(component, null, -1);
  390. }
  391. if (pages.size() == 1) {
  392. setSelectedIndex(0);
  393. }
  394. if (!haveRegistered && tip != null) {
  395. ToolTipManager.sharedInstance().registerComponent(this);
  396. haveRegistered = true;
  397. }
  398. if (accessibleContext != null) {
  399. accessibleContext.firePropertyChange(
  400. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  401. null, component);
  402. }
  403. revalidate();
  404. repaint();
  405. }
  406. /**
  407. * Adds a <i>component</i> and <i>tip</i> represented by a <i>title</i>
  408. * and/or <i>icon</i>, either of which can be null.
  409. * Cover method for insertTab().
  410. * @param title the title to be displayed in this tab
  411. * @param icon the icon to be displayed in this tab
  412. * @param component The component to be displayed when this tab is clicked.
  413. * @param tip the tooltip to be displayed for this tab
  414. *
  415. * @see #insertTab
  416. * @see #removeTabAt
  417. */
  418. public void addTab(String title, Icon icon, Component component, String tip) {
  419. insertTab(title, icon, component, tip, pages.size());
  420. }
  421. /**
  422. * Adds a <i>component</i> represented by a <i>title</i> and/or <i>icon</i>,
  423. * either of which can be null.
  424. * Cover method for insertTab().
  425. * @param title the title to be displayed in this tab
  426. * @param icon the icon to be displayed in this tab
  427. * @param component The component to be displayed when this tab is clicked.
  428. *
  429. * @see #insertTab
  430. * @see #removeTabAt
  431. */
  432. public void addTab(String title, Icon icon, Component component) {
  433. insertTab(title, icon, component, null, pages.size());
  434. }
  435. /**
  436. * Adds a <i>component</i> represented by a <i>title</i> and no icon.
  437. * Cover method for insertTab().
  438. * @param title the title to be displayed in this tab
  439. * @param component The component to be displayed when this tab is clicked.
  440. *
  441. * @see #insertTab
  442. * @see #removeTabAt
  443. */
  444. public void addTab(String title, Component component) {
  445. insertTab(title, null, component, null, pages.size());
  446. }
  447. /**
  448. * Adds a <i>component</i> with a tab title defaulting to
  449. * the name of the component.
  450. * Cover method for insertTab().
  451. * @param component The component to be displayed when this tab is clicked.
  452. *
  453. * @see #insertTab
  454. * @see #removeTabAt
  455. */
  456. public Component add(Component component) {
  457. addTab(component.getName(), component);
  458. return component;
  459. }
  460. /**
  461. * Adds a <i>component</i> with the specified tab title.
  462. * Cover method for insertTab().
  463. * @param title the title to be displayed in this tab
  464. * @param component The component to be displayed when this tab is clicked.
  465. *
  466. * @see #insertTab
  467. * @see #removeTabAt
  468. */
  469. public Component add(String title, Component component) {
  470. addTab(title, component);
  471. return component;
  472. }
  473. /**
  474. * Adds a <i>component</i> at the specified tab index with a tab
  475. * title defaulting to the name of the component.
  476. * Cover method for insertTab().
  477. * @param component The component to be displayed when this tab is clicked.
  478. * @param index the position to insert this new tab
  479. *
  480. * @see #insertTab
  481. * @see #removeTabAt
  482. */
  483. public Component add(Component component, int index) {
  484. insertTab(component.getName(), null, component, null, index);
  485. return component;
  486. }
  487. /**
  488. * Adds a <i>component</i> to the tabbed pane. If constraints
  489. * is a String or an Icon, it will be used for the tab title,
  490. * otherwise the component's name will be used as the tab title.
  491. * Cover method for insertTab().
  492. * @param component The component to be displayed when this tab is clicked.
  493. * @constraints the object to be displayed in the tab
  494. *
  495. * @see #insertTab
  496. * @see #removeTabAt
  497. */
  498. public void add(Component component, Object constraints) {
  499. if (constraints instanceof String) {
  500. addTab((String)constraints, component);
  501. } else if (constraints instanceof Icon) {
  502. addTab(null, (Icon)constraints, component);
  503. } else {
  504. add(component);
  505. }
  506. }
  507. /**
  508. * Adds a <i>component</i> at the specified tab index. If constraints
  509. * is a String or an Icon, it will be used for the tab title,
  510. * otherwise the component's name will be used as the tab title.
  511. * Cover method for insertTab().
  512. * @param component The component to be displayed when this tab is clicked.
  513. * @constraints the object to be displayed in the tab
  514. * @param index the position to insert this new tab
  515. *
  516. * @see #insertTab
  517. * @see #removeTabAt
  518. */
  519. public void add(Component component, Object constraints, int index) {
  520. Icon icon = constraints instanceof Icon? (Icon)constraints : null;
  521. String title = constraints instanceof String? (String)constraints : null;
  522. insertTab(title, icon, component, null, index);
  523. }
  524. /**
  525. * Removes the tab at <i>index</i>.
  526. * After the component associated with <i>index</i> is removed,
  527. * its visibility is reset to true to ensure it will be visible
  528. * if added to other containers.
  529. * @param index the index of the tab to be removed
  530. *
  531. * @see #addTab
  532. * @see #insertTab
  533. */
  534. public void removeTabAt(int index) {
  535. // If we are removing the currently selected tab AND
  536. // it happens to be the last tab in the bunch, then
  537. // select the previous tab
  538. int tabCount = getTabCount();
  539. int selected = getSelectedIndex();
  540. if (selected >= (tabCount - 1)) {
  541. setSelectedIndex(selected - 1);
  542. }
  543. Component component = getComponentAt(index);
  544. if (accessibleContext != null) {
  545. accessibleContext.firePropertyChange(
  546. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  547. component, null);
  548. }
  549. if (component != null) {
  550. super.remove(component);
  551. component.setVisible(true);
  552. }
  553. pages.removeElementAt(index);
  554. revalidate();
  555. repaint();
  556. }
  557. /**
  558. * Removes the tab which corresponds to the specified component.
  559. *
  560. * @param component the component to remove from the tabbedpane
  561. * @see #addTab
  562. * @see #removeTabAt
  563. */
  564. public void remove(Component component) {
  565. int index = indexOfComponent(component);
  566. if (index != -1) {
  567. removeTabAt(index);
  568. }
  569. }
  570. /**
  571. * Removes all the tabs from the tabbedpane.
  572. *
  573. * @see #addTab
  574. * @see #removeTabAt
  575. */
  576. public void removeAll() {
  577. setSelectedIndex(-1);
  578. int tabCount = getTabCount();
  579. for (int i = 0; i < tabCount; i++) {
  580. Component component = getComponentAt(i);
  581. if (accessibleContext != null) {
  582. accessibleContext.firePropertyChange(
  583. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  584. component, null);
  585. }
  586. }
  587. super.removeAll();
  588. pages.removeAllElements();
  589. revalidate();
  590. repaint();
  591. }
  592. /**
  593. * Returns the number of tabs in this tabbedpane.
  594. *
  595. * @return an int specifying the number of tabbed pages
  596. */
  597. public int getTabCount() {
  598. return pages.size();
  599. }
  600. /**
  601. * Returns the number of tab runs currently used to display
  602. * the tabs.
  603. * @return an int giving the number of rows if the tabPlacement
  604. * is TOP or BOTTOM and the number of columns if tabPlacement
  605. * is LEFT or RIGHT, or 0 if there is no UI set on this
  606. * tabbedpane
  607. */
  608. public int getTabRunCount() {
  609. if (ui != null) {
  610. return ((TabbedPaneUI)ui).getTabRunCount(this);
  611. }
  612. return 0;
  613. }
  614. // Getters for the Pages
  615. /**
  616. * Returns the tab title at <i>index</i>.
  617. *
  618. * @see #setTitleAt
  619. */
  620. public String getTitleAt(int index) {
  621. return ((Page)pages.elementAt(index)).title;
  622. }
  623. /**
  624. * Returns the tab icon at <i>index</i>.
  625. *
  626. * @see #setIconAt
  627. */
  628. public Icon getIconAt(int index) {
  629. return ((Page)pages.elementAt(index)).icon;
  630. }
  631. /**
  632. * Returns the tab disabled icon at <i>index</i>.
  633. *
  634. * @see #setDisabledIconAt
  635. */
  636. public Icon getDisabledIconAt(int index) {
  637. return ((Page)pages.elementAt(index)).disabledIcon;
  638. }
  639. /**
  640. * Returns the tab background color at <i>index</i>.
  641. *
  642. * @see #setBackgroundAt
  643. */
  644. public Color getBackgroundAt(int index) {
  645. return ((Page)pages.elementAt(index)).getBackground();
  646. }
  647. /**
  648. * Returns the tab foreground color at <i>index</i>.
  649. *
  650. * @see #setForegroundAt
  651. */
  652. public Color getForegroundAt(int index) {
  653. return ((Page)pages.elementAt(index)).getForeground();
  654. }
  655. /**
  656. * Returns whether or not the tab at <i>index</i> is
  657. * currently enabled.
  658. *
  659. * @see #setEnabledAt
  660. */
  661. public boolean isEnabledAt(int index) {
  662. return ((Page)pages.elementAt(index)).isEnabled();
  663. }
  664. /**
  665. * Returns the component at <i>index</i>.
  666. *
  667. * @see #setComponentAt
  668. */
  669. public Component getComponentAt(int index) {
  670. return ((Page)pages.elementAt(index)).component;
  671. }
  672. /**
  673. * Returns the tab bounds at <i>index</i>. If the tab at
  674. * this index is not currently visible in the UI, then returns null.
  675. * If there is no UI set on this tabbedpane, then returns null.
  676. */
  677. public Rectangle getBoundsAt(int index) {
  678. if (ui != null) {
  679. return ((TabbedPaneUI)ui).getTabBounds(this, index);
  680. }
  681. return null;
  682. }
  683. // Setters for the Pages
  684. /**
  685. * Sets the title at <i>index</i> to <i>title</i> which can be null.
  686. * An internal exception is raised if there is no tab at that index.
  687. * @param index the tab index where the title should be set
  688. * @param title the title to be displayed in the tab
  689. *
  690. * @see #getTitleAt
  691. * @beaninfo
  692. * preferred: true
  693. * attribute: visualUpdate true
  694. * description: The title at the specified tab index.
  695. */
  696. public void setTitleAt(int index, String title) {
  697. String oldTitle =((Page)pages.elementAt(index)).title;
  698. ((Page)pages.elementAt(index)).title = title;
  699. if ((oldTitle != title) && (accessibleContext != null)) {
  700. accessibleContext.firePropertyChange(
  701. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  702. oldTitle, title);
  703. }
  704. if (title == null || oldTitle == null ||
  705. !title.equals(oldTitle)) {
  706. revalidate();
  707. repaint();
  708. }
  709. }
  710. /**
  711. * Sets the icon at <i>index</i> to <i>icon</i> which can be null.
  712. * An internal exception is raised if there is no tab at that index.
  713. * @param index the tab index where the icon should be set
  714. * @param icon the icon to be displayed in the tab
  715. *
  716. * @see #getIconAt
  717. * @beaninfo
  718. * preferred: true
  719. * attribute: visualUpdate true
  720. * description: The icon at the specified tab index.
  721. */
  722. public void setIconAt(int index, Icon icon) {
  723. Icon oldIcon = ((Page)pages.elementAt(index)).icon;
  724. ((Page)pages.elementAt(index)).icon = icon;
  725. AccessibleContext ac = getAccessibleContext();
  726. // Fire the accessibility Visible data change
  727. if ((oldIcon != icon) && (accessibleContext != null)) {
  728. accessibleContext.firePropertyChange(
  729. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  730. oldIcon, icon);
  731. }
  732. if (icon != oldIcon) {
  733. revalidate();
  734. repaint();
  735. }
  736. }
  737. /**
  738. * Sets the disabled icon at <i>index</i> to <i>icon</i> which can be null.
  739. * An internal exception is raised if there is no tab at that index.
  740. * @param index the tab index where the disabled icon should be set
  741. * @param icon the icon to be displayed in the tab when disabled
  742. *
  743. * @see #getDisabledIconAt
  744. * @beaninfo
  745. * preferred: true
  746. * attribute: visualUpdate true
  747. * description: The disabled icon at the specified tab index.
  748. */
  749. public void setDisabledIconAt(int index, Icon disabledIcon) {
  750. Icon oldIcon = ((Page)pages.elementAt(index)).disabledIcon;
  751. ((Page)pages.elementAt(index)).disabledIcon = disabledIcon;
  752. if (disabledIcon != oldIcon && !isEnabledAt(index)) {
  753. revalidate();
  754. repaint();
  755. }
  756. }
  757. /**
  758. * Sets the background color at <i>index</i> to <i>background</i>
  759. * which can be null, in which case the tab's background color
  760. * will default to the background color of the tabbedpane.
  761. * An internal exception is raised if there is no tab at that index.
  762. * @param index the tab index where the background should be set
  763. * @param background the color to be displayed in the tab's background
  764. *
  765. * @see #getBackgroundAt
  766. * @beaninfo
  767. * preferred: true
  768. * attribute: visualUpdate true
  769. * description: The background color at the specified tab index.
  770. */
  771. public void setBackgroundAt(int index, Color background) {
  772. Color oldBg = ((Page)pages.elementAt(index)).background;
  773. ((Page)pages.elementAt(index)).setBackground(background);
  774. if (background == null || oldBg == null ||
  775. !background.equals(oldBg)) {
  776. Rectangle tabBounds = getBoundsAt(index);
  777. if (tabBounds != null) {
  778. repaint(tabBounds);
  779. }
  780. }
  781. }
  782. /**
  783. * Sets the foreground color at <i>index</i> to <i>foreground</i>
  784. * which can be null, in which case the tab's foreground color
  785. * will default to the foreground color of this tabbedpane.
  786. * An internal exception is raised if there is no tab at that index.
  787. * @param index the tab index where the foreground should be set
  788. * @param foreground the color to be displayed as the tab's foreground
  789. *
  790. * @see #getForegroundAt
  791. * @beaninfo
  792. * preferred: true
  793. * attribute: visualUpdate true
  794. * description: The foreground color at the specified tab index.
  795. */
  796. public void setForegroundAt(int index, Color foreground) {
  797. Color oldFg = ((Page)pages.elementAt(index)).foreground;
  798. ((Page)pages.elementAt(index)).setForeground(foreground);
  799. if (foreground == null || oldFg == null ||
  800. !foreground.equals(oldFg)) {
  801. Rectangle tabBounds = getBoundsAt(index);
  802. if (tabBounds != null) {
  803. repaint(tabBounds);
  804. }
  805. }
  806. }
  807. /**
  808. * Sets whether or not the tab at <i>index</i> is enabled.
  809. * An internal exception is raised if there is no tab at that index.
  810. * @param index the tab index which should be enabled/disabled
  811. * @param enabled whether or not the tab should be enabled
  812. *
  813. * @see #isEnabledAt
  814. */
  815. public void setEnabledAt(int index, boolean enabled) {
  816. boolean oldEnabled = ((Page)pages.elementAt(index)).isEnabled();
  817. ((Page)pages.elementAt(index)).setEnabled(enabled);
  818. if (enabled != oldEnabled) {
  819. repaint(getBoundsAt(index));
  820. }
  821. }
  822. /**
  823. * Sets the component at <i>index</i> to <i>component</i>.
  824. * An internal exception is raised if there is no tab at that index.
  825. * @param index the tab index where this component is being placed
  826. * @param component the component for the tab
  827. *
  828. * @see #getComponentAt
  829. * @beaninfo
  830. * attribute: visualUpdate true
  831. * description: The component at the specified tab index.
  832. */
  833. public void setComponentAt(int index, Component component) {
  834. Page page = (Page)pages.elementAt(index);
  835. if (component != page.component) {
  836. if (page.component != null) {
  837. // REMIND(aim): this is really silly;
  838. // why not if (page.component.getParent() == this) remove(component)
  839. synchronized(getTreeLock()) {
  840. int count = getComponentCount();
  841. Component children[] = getComponents();
  842. for (int i = 0; i < count; i++) {
  843. if (children[i] == page.component) {
  844. remove(i);
  845. }
  846. }
  847. }
  848. }
  849. page.component = component;
  850. component.setVisible(getSelectedIndex() == index);
  851. addImpl(component, null, -1);
  852. revalidate();
  853. }
  854. }
  855. /**
  856. * Returns the first tab index with a given <i>title</i>,
  857. * Returns -1 if no tab has this title.
  858. * @param title the title for the tab
  859. */
  860. public int indexOfTab(String title) {
  861. for(int i = 0; i < getTabCount(); i++) {
  862. if (getTitleAt(i).equals(title == null? "" : title)) {
  863. return i;
  864. }
  865. }
  866. return -1;
  867. }
  868. /**
  869. * Returns the first tab index with a given <i>icon</i>.
  870. * Returns -1 if no tab has this icon.
  871. * @param icon the icon for the tab
  872. */
  873. public int indexOfTab(Icon icon) {
  874. for(int i = 0; i < getTabCount(); i++) {
  875. if (getIconAt(i).equals(icon)) {
  876. return i;
  877. }
  878. }
  879. return -1;
  880. }
  881. /**
  882. * Returns the index of the tab for the specified component.
  883. * Returns -1 if there is no tab for this component.
  884. * @param component the component for the tab
  885. */
  886. public int indexOfComponent(Component component) {
  887. for(int i = 0; i < getTabCount(); i++) {
  888. Component c = getComponentAt(i);
  889. if (c != null) {
  890. if (c.equals(component)) {
  891. return i;
  892. }
  893. } else if (c == component) {
  894. return i;
  895. }
  896. }
  897. return -1;
  898. }
  899. /**
  900. * Returns the tooltip text for the component determined by the
  901. * mouse event location.
  902. *
  903. * @param event the MouseEvent that tells where the cursor is lingering
  904. * @return the String with the tooltip text
  905. */
  906. public String getToolTipText(MouseEvent event) {
  907. if (ui != null) {
  908. int index = ((TabbedPaneUI)ui).tabForCoordinate(this, event.getX(), event.getY());
  909. if (index != -1) {
  910. return ((Page)pages.elementAt(index)).tip;
  911. }
  912. }
  913. return super.getToolTipText(event);
  914. }
  915. /**
  916. * See readObject() and writeObject() in JComponent for more
  917. * information about serialization in Swing.
  918. */
  919. private void writeObject(ObjectOutputStream s) throws IOException {
  920. s.defaultWriteObject();
  921. if ((ui != null) && (getUIClassID().equals(uiClassID))) {
  922. ui.installUI(this);
  923. }
  924. }
  925. /**
  926. * Returns a string representation of this JTabbedPane. This method
  927. * is intended to be used only for debugging purposes, and the
  928. * content and format of the returned string may vary between
  929. * implementations. The returned string may be empty but may not
  930. * be <code>null</code>.
  931. *
  932. * @return a string representation of this JTabbedPane.
  933. */
  934. protected String paramString() {
  935. String tabPlacementString;
  936. if (tabPlacement == TOP) {
  937. tabPlacementString = "TOP";
  938. } else if (tabPlacement == BOTTOM) {
  939. tabPlacementString = "BOTTOM";
  940. } else if (tabPlacement == LEFT) {
  941. tabPlacementString = "LEFT";
  942. } else if (tabPlacement == RIGHT) {
  943. tabPlacementString = "RIGHT";
  944. } else tabPlacementString = "";
  945. String haveRegisteredString = (haveRegistered ?
  946. "true" : "false");
  947. return super.paramString() +
  948. ",haveRegistered=" + haveRegisteredString +
  949. ",tabPlacement=" + tabPlacementString;
  950. }
  951. /////////////////
  952. // Accessibility support
  953. ////////////////
  954. /**
  955. * Get the AccessibleContext associated with this JComponent
  956. *
  957. * @return the AccessibleContext of this JComponent
  958. */
  959. public AccessibleContext getAccessibleContext() {
  960. if (accessibleContext == null) {
  961. accessibleContext = new AccessibleJTabbedPane();
  962. }
  963. return accessibleContext;
  964. }
  965. /**
  966. * The class used to obtain the accessible role for this object.
  967. * <p>
  968. * <strong>Warning:</strong>
  969. * Serialized objects of this class will not be compatible with
  970. * future Swing releases. The current serialization support is appropriate
  971. * for short term storage or RMI between applications running the same
  972. * version of Swing. A future release of Swing will provide support for
  973. * long term persistence.
  974. */
  975. protected class AccessibleJTabbedPane extends AccessibleJComponent
  976. implements AccessibleSelection, ChangeListener {
  977. /**
  978. * Constructs an AccessibleJTabbedPane
  979. */
  980. public AccessibleJTabbedPane() {
  981. super();
  982. JTabbedPane.this.model.addChangeListener(this);
  983. }
  984. public void stateChanged(ChangeEvent e) {
  985. Object o = e.getSource();
  986. firePropertyChange(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY,
  987. null, o);
  988. }
  989. /**
  990. * Get the role of this object.
  991. *
  992. * @return an instance of AccessibleRole describing the role of
  993. * the object
  994. */
  995. public AccessibleRole getAccessibleRole() {
  996. return AccessibleRole.PAGE_TAB_LIST;
  997. }
  998. /**
  999. * Returns the number of accessible children in the object.
  1000. *
  1001. * @return the number of accessible children in the object.
  1002. */
  1003. public int getAccessibleChildrenCount() {
  1004. return getTabCount();
  1005. }
  1006. /**
  1007. * Return the specified Accessible child of the object.
  1008. *
  1009. * @param i zero-based index of child
  1010. * @return the Accessible child of the object
  1011. */
  1012. public Accessible getAccessibleChild(int i) {
  1013. if (i < 0 || i >= getTabCount()) {
  1014. return null;
  1015. }
  1016. return (Accessible) pages.elementAt(i);
  1017. }
  1018. /**
  1019. * Get the AccessibleSelection associated with this object if one
  1020. * exists. Otherwise return null.
  1021. *
  1022. * @return the AccessibleSelection, or null
  1023. */
  1024. public AccessibleSelection getAccessibleSelection() {
  1025. return this;
  1026. }
  1027. public Accessible getAccessibleAt(Point p) {
  1028. int tab = ((TabbedPaneUI) ui).tabForCoordinate(JTabbedPane.this,
  1029. p.x, p.y);
  1030. if (tab == -1) {
  1031. tab = getSelectedIndex();
  1032. }
  1033. return getAccessibleChild(tab);
  1034. }
  1035. public int getAccessibleSelectionCount() {
  1036. return 1;
  1037. }
  1038. public Accessible getAccessibleSelection(int i) {
  1039. int index = getSelectedIndex();
  1040. if (index == -1) {
  1041. return null;
  1042. }
  1043. return (Accessible) pages.elementAt(index);
  1044. }
  1045. public boolean isAccessibleChildSelected(int i) {
  1046. return (i == getSelectedIndex());
  1047. }
  1048. public void addAccessibleSelection(int i) {
  1049. setSelectedIndex(i);
  1050. }
  1051. public void removeAccessibleSelection(int i) {
  1052. // can't do
  1053. }
  1054. public void clearAccessibleSelection() {
  1055. // can't do
  1056. }
  1057. public void selectAllAccessibleSelection() {
  1058. // can't do
  1059. }
  1060. }
  1061. private class Page extends AccessibleContext
  1062. implements Serializable, Accessible, AccessibleComponent {
  1063. String title;
  1064. Color background;
  1065. Color foreground;
  1066. Icon icon;
  1067. Icon disabledIcon;
  1068. JTabbedPane parent;
  1069. Component component;
  1070. String tip;
  1071. boolean enabled = true;
  1072. boolean needsUIUpdate;
  1073. Page(JTabbedPane parent,
  1074. String title, Icon icon, Icon disabledIcon, Component component, String tip) {
  1075. this.title = title;
  1076. this.icon = icon;
  1077. this.disabledIcon = disabledIcon;
  1078. this.parent = parent;
  1079. this.setAccessibleParent(parent);
  1080. this.component = component;
  1081. this.tip = tip;
  1082. if (component instanceof Accessible) {
  1083. AccessibleContext ac;
  1084. ac = ((Accessible) component).getAccessibleContext();
  1085. if (ac != null) {
  1086. ac.setAccessibleParent(this);
  1087. }
  1088. }
  1089. }
  1090. /////////////////
  1091. // Accessibility support
  1092. ////////////////
  1093. public AccessibleContext getAccessibleContext() {
  1094. return this;
  1095. }
  1096. // AccessibleContext methods
  1097. public String getAccessibleName() {
  1098. if (accessibleName != null) {
  1099. return accessibleName;
  1100. } else if (title != null) {
  1101. return title;
  1102. }
  1103. return null;
  1104. }
  1105. public String getAccessibleDescription() {
  1106. if (accessibleDescription != null) {
  1107. return accessibleDescription;
  1108. } else if (tip != null) {
  1109. return tip;
  1110. }
  1111. return null;
  1112. }
  1113. public AccessibleRole getAccessibleRole() {
  1114. return AccessibleRole.PAGE_TAB;
  1115. }
  1116. public AccessibleStateSet getAccessibleStateSet() {
  1117. AccessibleStateSet states;
  1118. states = parent.getAccessibleContext().getAccessibleStateSet();
  1119. states.add(AccessibleState.SELECTABLE);
  1120. int i = parent.indexOfTab(title);
  1121. if (i == parent.getSelectedIndex()) {
  1122. states.add(AccessibleState.SELECTED);
  1123. }
  1124. return states;
  1125. }
  1126. public int getAccessibleIndexInParent() {
  1127. return parent.indexOfTab(title);
  1128. }
  1129. public int getAccessibleChildrenCount() {
  1130. if (component instanceof Accessible) {
  1131. return 1;
  1132. } else {
  1133. return 0;
  1134. }
  1135. }
  1136. public Accessible getAccessibleChild(int i) {
  1137. if (component instanceof Accessible) {
  1138. return (Accessible) component;
  1139. } else {
  1140. return null;
  1141. }
  1142. }
  1143. public Locale getLocale() {
  1144. return parent.getLocale();
  1145. }
  1146. public AccessibleComponent getAccessibleComponent() {
  1147. return this;
  1148. }
  1149. // AccessibleComponent methods
  1150. public Color getBackground() {
  1151. return background != null? background : parent.getBackground();
  1152. }
  1153. public void setBackground(Color c) {
  1154. background = c;
  1155. }
  1156. public Color getForeground() {
  1157. return foreground != null? foreground : parent.getForeground();
  1158. }
  1159. public void setForeground(Color c) {
  1160. foreground = c;
  1161. }
  1162. public Cursor getCursor() {
  1163. return parent.getCursor();
  1164. }
  1165. public void setCursor(Cursor c) {
  1166. parent.setCursor(c);
  1167. }
  1168. public Font getFont() {
  1169. return parent.getFont();
  1170. }
  1171. public void setFont(Font f) {
  1172. parent.setFont(f);
  1173. }
  1174. public FontMetrics getFontMetrics(Font f) {
  1175. return parent.getFontMetrics(f);
  1176. }
  1177. public boolean isEnabled() {
  1178. return enabled;
  1179. }
  1180. public void setEnabled(boolean b) {
  1181. enabled = b;
  1182. }
  1183. public boolean isVisible() {
  1184. return parent.isVisible();
  1185. }
  1186. public void setVisible(boolean b) {
  1187. parent.setVisible(b);
  1188. }
  1189. public boolean isShowing() {
  1190. return parent.isShowing();
  1191. }
  1192. public boolean contains(Point p) {
  1193. Rectangle r = getBounds();
  1194. return r.contains(p);
  1195. }
  1196. public Point getLocationOnScreen() {
  1197. Point parentLocation = parent.getLocationOnScreen();
  1198. Point componentLocation = getLocation();
  1199. componentLocation.translate(parentLocation.x, parentLocation.y);
  1200. return componentLocation;
  1201. }
  1202. public Point getLocation() {
  1203. Rectangle r = getBounds();
  1204. return new Point(r.x, r.y);
  1205. }
  1206. public void setLocation(Point p) {
  1207. // do nothing
  1208. }
  1209. public Rectangle getBounds() {
  1210. return parent.getUI().getTabBounds(parent,
  1211. parent.indexOfTab(title));
  1212. }
  1213. public void setBounds(Rectangle r) {
  1214. // do nothing
  1215. }
  1216. public Dimension getSize() {
  1217. Rectangle r = getBounds();
  1218. return new Dimension(r.width, r.height);
  1219. }
  1220. public void setSize(Dimension d) {
  1221. // do nothing
  1222. }
  1223. public Accessible getAccessibleAt(Point p) {
  1224. if (component instanceof Accessible) {
  1225. return (Accessible) component;
  1226. } else {
  1227. return null;
  1228. }
  1229. }
  1230. public boolean isFocusTraversable() {
  1231. return false;
  1232. }
  1233. public void requestFocus() {
  1234. // do nothing
  1235. }
  1236. public void addFocusListener(FocusListener l) {
  1237. // do nothing
  1238. }
  1239. public void removeFocusListener(FocusListener l) {
  1240. // do nothing
  1241. }
  1242. }
  1243. }