1. /*
  2. * @(#)DefaultTableColumnModel.java 1.45 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.table;
  8. import javax.swing.*;
  9. import javax.swing.event.*;
  10. import java.awt.*;
  11. import java.util.Vector;
  12. import java.util.Enumeration;
  13. import java.util.EventListener;
  14. import java.beans.PropertyChangeListener;
  15. import java.beans.PropertyChangeEvent;
  16. import java.io.Serializable;
  17. /**
  18. * The standard column-handler for a <code>JTable</code>.
  19. * <p>
  20. * <strong>Warning:</strong>
  21. * Serialized objects of this class will not be compatible with
  22. * future Swing releases. The current serialization support is
  23. * appropriate for short term storage or RMI between applications running
  24. * the same version of Swing. As of 1.4, support for long term storage
  25. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  26. * has been added to the <code>java.beans</code> package.
  27. * Please see {@link java.beans.XMLEncoder}.
  28. *
  29. * @version 1.45 01/23/03
  30. * @author Alan Chung
  31. * @author Philip Milne
  32. * @see JTable
  33. */
  34. public class DefaultTableColumnModel implements TableColumnModel,
  35. PropertyChangeListener, ListSelectionListener, Serializable
  36. {
  37. //
  38. // Instance Variables
  39. //
  40. /** Array of TableColumn objects in this model */
  41. protected Vector tableColumns;
  42. /** Model for keeping track of column selections */
  43. protected ListSelectionModel selectionModel;
  44. /** Width margin between each column */
  45. protected int columnMargin;
  46. /** List of TableColumnModelListener */
  47. protected EventListenerList listenerList = new EventListenerList();
  48. /** Change event (only one needed) */
  49. transient protected ChangeEvent changeEvent = null;
  50. /** Column selection allowed in this column model */
  51. protected boolean columnSelectionAllowed;
  52. /** A local cache of the combined width of all columns */
  53. protected int totalColumnWidth;
  54. //
  55. // Constructors
  56. //
  57. /**
  58. * Creates a default table column model.
  59. */
  60. public DefaultTableColumnModel() {
  61. super();
  62. // Initialize local ivars to default
  63. tableColumns = new Vector();
  64. setSelectionModel(createSelectionModel());
  65. getSelectionModel().setAnchorSelectionIndex(0);
  66. setColumnMargin(1);
  67. invalidateWidthCache();
  68. setColumnSelectionAllowed(false);
  69. }
  70. //
  71. // Modifying the model
  72. //
  73. /**
  74. * Appends <code>aColumn</code> to the end of the
  75. * <code>tableColumns</code> array.
  76. * This method also posts the <code>columnAdded</code>
  77. * event to its listeners.
  78. *
  79. * @param aColumn the <code>TableColumn</code> to be added
  80. * @exception IllegalArgumentException if <code>aColumn</code> is
  81. * <code>null</code>
  82. * @see #removeColumn
  83. */
  84. public void addColumn(TableColumn aColumn) {
  85. if (aColumn == null) {
  86. throw new IllegalArgumentException("Object is null");
  87. }
  88. tableColumns.addElement(aColumn);
  89. aColumn.addPropertyChangeListener(this);
  90. invalidateWidthCache();
  91. // Post columnAdded event notification
  92. fireColumnAdded(new TableColumnModelEvent(this, 0,
  93. getColumnCount() - 1));
  94. }
  95. /**
  96. * Deletes the <code>column</code> from the
  97. * <code>tableColumns</code> array. This method will do nothing if
  98. * <code>column</code> is not in the table's columns list.
  99. * <code>tile</code> is called
  100. * to resize both the header and table views.
  101. * This method also posts a <code>columnRemoved</code>
  102. * event to its listeners.
  103. *
  104. * @param column the <code>TableColumn</code> to be removed
  105. * @see #addColumn
  106. */
  107. public void removeColumn(TableColumn column) {
  108. int columnIndex = tableColumns.indexOf(column);
  109. if (columnIndex != -1) {
  110. // Adjust for the selection
  111. if (selectionModel != null) {
  112. selectionModel.removeIndexInterval(columnIndex,columnIndex);
  113. }
  114. column.removePropertyChangeListener(this);
  115. tableColumns.removeElementAt(columnIndex);
  116. invalidateWidthCache();
  117. // Post columnAdded event notification. (JTable and JTableHeader
  118. // listens so they can adjust size and redraw)
  119. fireColumnRemoved(new TableColumnModelEvent(this,
  120. columnIndex, 0));
  121. }
  122. }
  123. /**
  124. * Moves the column and heading at <code>columnIndex</code> to
  125. * <code>newIndex</code>. The old column at <code>columnIndex</code>
  126. * will now be found at <code>newIndex</code>. The column
  127. * that used to be at <code>newIndex</code> is shifted
  128. * left or right to make room. This will not move any columns if
  129. * <code>columnIndex</code> equals <code>newIndex</code>. This method
  130. * also posts a <code>columnMoved</code> event to its listeners.
  131. *
  132. * @param columnIndex the index of column to be moved
  133. * @param newIndex new index to move the column
  134. * @exception IllegalArgumentException if <code>column</code> or
  135. * <code>newIndex</code>
  136. * are not in the valid range
  137. */
  138. public void moveColumn(int columnIndex, int newIndex) {
  139. if ((columnIndex < 0) || (columnIndex >= getColumnCount()) ||
  140. (newIndex < 0) || (newIndex >= getColumnCount()))
  141. throw new IllegalArgumentException("moveColumn() - Index out of range");
  142. TableColumn aColumn;
  143. // If the column has not yet moved far enough to change positions
  144. // post the event anyway, the "draggedDistance" property of the
  145. // tableHeader will say how far the column has been dragged.
  146. // Here we are really trying to get the best out of an
  147. // API that could do with some rethinking. We preserve backward
  148. // compatibility by slightly bending the meaning of these methods.
  149. if (columnIndex == newIndex) {
  150. fireColumnMoved(new TableColumnModelEvent(this, columnIndex, newIndex));
  151. return;
  152. }
  153. aColumn = (TableColumn)tableColumns.elementAt(columnIndex);
  154. tableColumns.removeElementAt(columnIndex);
  155. boolean selected = selectionModel.isSelectedIndex(columnIndex);
  156. selectionModel.removeIndexInterval(columnIndex,columnIndex);
  157. tableColumns.insertElementAt(aColumn, newIndex);
  158. selectionModel.insertIndexInterval(newIndex, 1, true);
  159. if (selected) {
  160. selectionModel.addSelectionInterval(newIndex, newIndex);
  161. }
  162. else {
  163. selectionModel.removeSelectionInterval(newIndex, newIndex);
  164. }
  165. fireColumnMoved(new TableColumnModelEvent(this, columnIndex,
  166. newIndex));
  167. }
  168. /**
  169. * Sets the column margin to <code>newMargin</code>. This method
  170. * also posts a <code>columnMarginChanged</code> event to its
  171. * listeners.
  172. *
  173. * @param newMargin the new margin width, in pixels
  174. * @see #getColumnMargin
  175. * @see #getTotalColumnWidth
  176. */
  177. public void setColumnMargin(int newMargin) {
  178. if (newMargin != columnMargin) {
  179. columnMargin = newMargin;
  180. // Post columnMarginChanged event notification.
  181. fireColumnMarginChanged();
  182. }
  183. }
  184. //
  185. // Querying the model
  186. //
  187. /**
  188. * Returns the number of columns in the <code>tableColumns</code> array.
  189. *
  190. * @return the number of columns in the <code>tableColumns</code> array
  191. * @see #getColumns
  192. */
  193. public int getColumnCount() {
  194. return tableColumns.size();
  195. }
  196. /**
  197. * Returns an <code>Enumeration</code> of all the columns in the model.
  198. * @return an <code>Enumeration</code> of the columns in the model
  199. */
  200. public Enumeration getColumns() {
  201. return tableColumns.elements();
  202. }
  203. /**
  204. * Returns the index of the first column in the <code>tableColumns</code>
  205. * array whose identifier is equal to <code>identifier</code>,
  206. * when compared using <code>equals</code>.
  207. *
  208. * @param identifier the identifier object
  209. * @return the index of the first column in the
  210. * <code>tableColumns</code> array whose identifier
  211. * is equal to <code>identifier</code>
  212. * @exception IllegalArgumentException if <code>identifier</code>
  213. * is <code>null</code>, or if no
  214. * <code>TableColumn</code> has this
  215. * <code>identifier</code>
  216. * @see #getColumn
  217. */
  218. public int getColumnIndex(Object identifier) {
  219. if (identifier == null) {
  220. throw new IllegalArgumentException("Identifier is null");
  221. }
  222. Enumeration enumeration = getColumns();
  223. TableColumn aColumn;
  224. int index = 0;
  225. while (enumeration.hasMoreElements()) {
  226. aColumn = (TableColumn)enumeration.nextElement();
  227. // Compare them this way in case the column's identifier is null.
  228. if (identifier.equals(aColumn.getIdentifier()))
  229. return index;
  230. index++;
  231. }
  232. throw new IllegalArgumentException("Identifier not found");
  233. }
  234. /**
  235. * Returns the <code>TableColumn</code> object for the column
  236. * at <code>columnIndex</code>.
  237. *
  238. * @param columnIndex the index of the column desired
  239. * @return the <code>TableColumn</code> object for the column
  240. * at <code>columnIndex</code>
  241. */
  242. public TableColumn getColumn(int columnIndex) {
  243. return (TableColumn)tableColumns.elementAt(columnIndex);
  244. }
  245. /**
  246. * Returns the width margin for <code>TableColumn</code>.
  247. * The default <code>columnMargin</code> is 1.
  248. *
  249. * @return the maximum width for the <code>TableColumn</code>
  250. * @see #setColumnMargin
  251. */
  252. public int getColumnMargin() {
  253. return columnMargin;
  254. }
  255. /**
  256. * Returns the index of the column that lies at position <code>x</code>,
  257. * or -1 if no column covers this point.
  258. *
  259. * In keeping with Swing's separable model architecture, a
  260. * TableColumnModel does not know how the table columns actually appear on
  261. * screen. The visual presentation of the columns is the responsibility
  262. * of the view/controller object using this model (typically JTable). The
  263. * view/controller need not display the columns sequentially from left to
  264. * right. For example, columns could be displayed from right to left to
  265. * accomodate a locale preference or some columns might be hidden at the
  266. * request of the user. Because the model does not know how the columns
  267. * are laid out on screen, the given <code>xPosition</code> should not be
  268. * considered to be a coordinate in 2D graphics space. Instead, it should
  269. * be considered to be a width from the start of the first column in the
  270. * model. If the column index for a given X coordinate in 2D space is
  271. * required, <code>JTable.columnAtPoint</code> can be used instead.
  272. *
  273. * @param x the horizontal location of interest
  274. * @return the index of the column or -1 if no column is found
  275. * @see javax.swing.JTable#columnAtPoint
  276. */
  277. public int getColumnIndexAtX(int x) {
  278. if (x < 0) {
  279. return -1;
  280. }
  281. int cc = getColumnCount();
  282. for(int column = 0; column < cc; column++) {
  283. x = x - getColumn(column).getWidth();
  284. if (x < 0) {
  285. return column;
  286. }
  287. }
  288. return -1;
  289. }
  290. /**
  291. * Returns the total combined width of all columns.
  292. * @return the <code>totalColumnWidth</code> property
  293. */
  294. public int getTotalColumnWidth() {
  295. if (totalColumnWidth == -1) {
  296. recalcWidthCache();
  297. }
  298. return totalColumnWidth;
  299. }
  300. //
  301. // Selection model
  302. //
  303. /**
  304. * Sets the selection model for this <code>TableColumnModel</code>
  305. * to <code>newModel</code>
  306. * and registers for listener notifications from the new selection
  307. * model. If <code>newModel</code> is <code>null</code>,
  308. * an exception is thrown.
  309. *
  310. * @param newModel the new selection model
  311. * @exception IllegalArgumentException if <code>newModel</code>
  312. * is <code>null</code>
  313. * @see #getSelectionModel
  314. */
  315. public void setSelectionModel(ListSelectionModel newModel) {
  316. if (newModel == null) {
  317. throw new IllegalArgumentException("Cannot set a null SelectionModel");
  318. }
  319. ListSelectionModel oldModel = selectionModel;
  320. if (newModel != oldModel) {
  321. if (oldModel != null) {
  322. oldModel.removeListSelectionListener(this);
  323. }
  324. selectionModel= newModel;
  325. if (newModel != null) {
  326. newModel.addListSelectionListener(this);
  327. }
  328. }
  329. }
  330. /**
  331. * Returns the <code>ListSelectionModel</code> that is used to
  332. * maintain column selection state.
  333. *
  334. * @return the object that provides column selection state. Or
  335. * <code>null</code> if row selection is not allowed.
  336. * @see #setSelectionModel
  337. */
  338. public ListSelectionModel getSelectionModel() {
  339. return selectionModel;
  340. }
  341. // implements javax.swing.table.TableColumnModel
  342. /**
  343. * Sets whether column selection is allowed. The default is false.
  344. * @param flag true if column selection will be allowed, false otherwise
  345. */
  346. public void setColumnSelectionAllowed(boolean flag) {
  347. columnSelectionAllowed = flag;
  348. }
  349. // implements javax.swing.table.TableColumnModel
  350. /**
  351. * Returns true if column selection is allowed, otherwise false.
  352. * The default is false.
  353. * @return the <code>columnSelectionAllowed</code> property
  354. */
  355. public boolean getColumnSelectionAllowed() {
  356. return columnSelectionAllowed;
  357. }
  358. // implements javax.swing.table.TableColumnModel
  359. /**
  360. * Returns an array of selected columns. If <code>selectionModel</code>
  361. * is <code>null</code>, returns an empty array.
  362. * @return an array of selected columns or an empty array if nothing
  363. * is selected or the <code>selectionModel</code> is
  364. * <code>null</code>
  365. */
  366. public int[] getSelectedColumns() {
  367. if (selectionModel != null) {
  368. int iMin = selectionModel.getMinSelectionIndex();
  369. int iMax = selectionModel.getMaxSelectionIndex();
  370. if ((iMin == -1) || (iMax == -1)) {
  371. return new int[0];
  372. }
  373. int[] rvTmp = new int[1+ (iMax - iMin)];
  374. int n = 0;
  375. for(int i = iMin; i <= iMax; i++) {
  376. if (selectionModel.isSelectedIndex(i)) {
  377. rvTmp[n++] = i;
  378. }
  379. }
  380. int[] rv = new int[n];
  381. System.arraycopy(rvTmp, 0, rv, 0, n);
  382. return rv;
  383. }
  384. return new int[0];
  385. }
  386. // implements javax.swing.table.TableColumnModel
  387. /**
  388. * Returns the number of columns selected.
  389. * @return the number of columns selected
  390. */
  391. public int getSelectedColumnCount() {
  392. if (selectionModel != null) {
  393. int iMin = selectionModel.getMinSelectionIndex();
  394. int iMax = selectionModel.getMaxSelectionIndex();
  395. int count = 0;
  396. for(int i = iMin; i <= iMax; i++) {
  397. if (selectionModel.isSelectedIndex(i)) {
  398. count++;
  399. }
  400. }
  401. return count;
  402. }
  403. return 0;
  404. }
  405. //
  406. // Listener Support Methods
  407. //
  408. // implements javax.swing.table.TableColumnModel
  409. /**
  410. * Adds a listener for table column model events.
  411. * @param x a <code>TableColumnModelListener</code> object
  412. */
  413. public void addColumnModelListener(TableColumnModelListener x) {
  414. listenerList.add(TableColumnModelListener.class, x);
  415. }
  416. // implements javax.swing.table.TableColumnModel
  417. /**
  418. * Removes a listener for table column model events.
  419. * @param x a <code>TableColumnModelListener</code> object
  420. */
  421. public void removeColumnModelListener(TableColumnModelListener x) {
  422. listenerList.remove(TableColumnModelListener.class, x);
  423. }
  424. /**
  425. * Returns an array of all the column model listeners
  426. * registered on this model.
  427. *
  428. * @return all of this default table column model's <code>ColumnModelListener</code>s
  429. * or an empty
  430. * array if no column model listeners are currently registered
  431. *
  432. * @see #addColumnModelListener
  433. * @see #removeColumnModelListener
  434. *
  435. * @since 1.4
  436. */
  437. public TableColumnModelListener[] getColumnModelListeners() {
  438. return (TableColumnModelListener[])listenerList.getListeners(
  439. TableColumnModelListener.class);
  440. }
  441. //
  442. // Event firing methods
  443. //
  444. /**
  445. * Notifies all listeners that have registered interest for
  446. * notification on this event type. The event instance
  447. * is lazily created using the parameters passed into
  448. * the fire method.
  449. * @param e the event received
  450. * @see EventListenerList
  451. */
  452. protected void fireColumnAdded(TableColumnModelEvent e) {
  453. // Guaranteed to return a non-null array
  454. Object[] listeners = listenerList.getListenerList();
  455. // Process the listeners last to first, notifying
  456. // those that are interested in this event
  457. for (int i = listeners.length-2; i>=0; i-=2) {
  458. if (listeners[i]==TableColumnModelListener.class) {
  459. // Lazily create the event:
  460. // if (e == null)
  461. // e = new ChangeEvent(this);
  462. ((TableColumnModelListener)listeners[i+1]).
  463. columnAdded(e);
  464. }
  465. }
  466. }
  467. /**
  468. * Notifies all listeners that have registered interest for
  469. * notification on this event type. The event instance
  470. * is lazily created using the parameters passed into
  471. * the fire method.
  472. * @param e the event received
  473. * @see EventListenerList
  474. */
  475. protected void fireColumnRemoved(TableColumnModelEvent e) {
  476. // Guaranteed to return a non-null array
  477. Object[] listeners = listenerList.getListenerList();
  478. // Process the listeners last to first, notifying
  479. // those that are interested in this event
  480. for (int i = listeners.length-2; i>=0; i-=2) {
  481. if (listeners[i]==TableColumnModelListener.class) {
  482. // Lazily create the event:
  483. // if (e == null)
  484. // e = new ChangeEvent(this);
  485. ((TableColumnModelListener)listeners[i+1]).
  486. columnRemoved(e);
  487. }
  488. }
  489. }
  490. /**
  491. * Notifies all listeners that have registered interest for
  492. * notification on this event type. The event instance
  493. * is lazily created using the parameters passed into
  494. * the fire method.
  495. * @param e the event received
  496. * @see EventListenerList
  497. */
  498. protected void fireColumnMoved(TableColumnModelEvent e) {
  499. // Guaranteed to return a non-null array
  500. Object[] listeners = listenerList.getListenerList();
  501. // Process the listeners last to first, notifying
  502. // those that are interested in this event
  503. for (int i = listeners.length-2; i>=0; i-=2) {
  504. if (listeners[i]==TableColumnModelListener.class) {
  505. // Lazily create the event:
  506. // if (e == null)
  507. // e = new ChangeEvent(this);
  508. ((TableColumnModelListener)listeners[i+1]).
  509. columnMoved(e);
  510. }
  511. }
  512. }
  513. /**
  514. * Notifies all listeners that have registered interest for
  515. * notification on this event type. The event instance
  516. * is lazily created using the parameters passed into
  517. * the fire method.
  518. * @param e the event received
  519. * @see EventListenerList
  520. */
  521. protected void fireColumnSelectionChanged(ListSelectionEvent e) {
  522. // Guaranteed to return a non-null array
  523. Object[] listeners = listenerList.getListenerList();
  524. // Process the listeners last to first, notifying
  525. // those that are interested in this event
  526. for (int i = listeners.length-2; i>=0; i-=2) {
  527. if (listeners[i]==TableColumnModelListener.class) {
  528. // Lazily create the event:
  529. // if (e == null)
  530. // e = new ChangeEvent(this);
  531. ((TableColumnModelListener)listeners[i+1]).
  532. columnSelectionChanged(e);
  533. }
  534. }
  535. }
  536. /**
  537. * Notifies all listeners that have registered interest for
  538. * notification on this event type. The event instance
  539. * is lazily created using the parameters passed into
  540. * the fire method.
  541. * @see EventListenerList
  542. */
  543. protected void fireColumnMarginChanged() {
  544. // Guaranteed to return a non-null array
  545. Object[] listeners = listenerList.getListenerList();
  546. // Process the listeners last to first, notifying
  547. // those that are interested in this event
  548. for (int i = listeners.length-2; i>=0; i-=2) {
  549. if (listeners[i]==TableColumnModelListener.class) {
  550. // Lazily create the event:
  551. if (changeEvent == null)
  552. changeEvent = new ChangeEvent(this);
  553. ((TableColumnModelListener)listeners[i+1]).
  554. columnMarginChanged(changeEvent);
  555. }
  556. }
  557. }
  558. /**
  559. * Returns an array of all the objects currently registered
  560. * as <code><em>Foo</em>Listener</code>s
  561. * upon this model.
  562. * <code><em>Foo</em>Listener</code>s are registered using the
  563. * <code>add<em>Foo</em>Listener</code> method.
  564. *
  565. * <p>
  566. *
  567. * You can specify the <code>listenerType</code> argument
  568. * with a class literal,
  569. * such as
  570. * <code><em>Foo</em>Listener.class</code>.
  571. * For example, you can query a
  572. * <code>DefaultTableColumnModel</code> <code>m</code>
  573. * for its column model listeners with the following code:
  574. *
  575. * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre>
  576. *
  577. * If no such listeners exist, this method returns an empty array.
  578. *
  579. * @param listenerType the type of listeners requested; this parameter
  580. * should specify an interface that descends from
  581. * <code>java.util.EventListener</code>
  582. * @return an array of all objects registered as
  583. * <code><em>Foo</em>Listener</code>s on this model,
  584. * or an empty array if no such
  585. * listeners have been added
  586. * @exception ClassCastException if <code>listenerType</code>
  587. * doesn't specify a class or interface that implements
  588. * <code>java.util.EventListener</code>
  589. *
  590. * @see #getColumnModelListeners
  591. * @since 1.3
  592. */
  593. public EventListener[] getListeners(Class listenerType) {
  594. return listenerList.getListeners(listenerType);
  595. }
  596. //
  597. // Implementing the PropertyChangeListener interface
  598. //
  599. // PENDING(alan)
  600. // implements java.beans.PropertyChangeListener
  601. /**
  602. * Property Change Listener change method. Used to track changes
  603. * to the column width or preferred column width.
  604. *
  605. * @param evt <code>PropertyChangeEvent</code>
  606. */
  607. public void propertyChange(PropertyChangeEvent evt) {
  608. String name = evt.getPropertyName();
  609. if (name == "width" || name == "preferredWidth") {
  610. invalidateWidthCache();
  611. // This is a misnomer, we're using this method
  612. // simply to cause a relayout.
  613. fireColumnMarginChanged();
  614. }
  615. }
  616. //
  617. // Implementing ListSelectionListener interface
  618. //
  619. // implements javax.swing.event.ListSelectionListener
  620. /**
  621. * A <code>ListSelectionListener</code> that forwards
  622. * <code>ListSelectionEvents</code> when there is a column
  623. * selection change.
  624. *
  625. * @param e the change event
  626. */
  627. public void valueChanged(ListSelectionEvent e) {
  628. fireColumnSelectionChanged(e);
  629. }
  630. //
  631. // Protected Methods
  632. //
  633. /**
  634. * Creates a new default list selection model.
  635. */
  636. protected ListSelectionModel createSelectionModel() {
  637. return new DefaultListSelectionModel();
  638. }
  639. /**
  640. * Recalculates the total combined width of all columns. Updates the
  641. * <code>totalColumnWidth</code> property.
  642. */
  643. protected void recalcWidthCache() {
  644. Enumeration enumeration = getColumns();
  645. totalColumnWidth = 0;
  646. while (enumeration.hasMoreElements()) {
  647. totalColumnWidth += ((TableColumn)enumeration.nextElement()).getWidth();
  648. }
  649. }
  650. private void invalidateWidthCache() {
  651. totalColumnWidth = -1;
  652. }
  653. } // End of class DefaultTableColumnModel