1. /*
  2. * @(#)DefaultTableColumnModel.java 1.35 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.table;
  11. import javax.swing.*;
  12. import javax.swing.event.*;
  13. import java.awt.*;
  14. import java.util.Vector;
  15. import java.util.Enumeration;
  16. import java.util.EventListener;
  17. import java.beans.PropertyChangeListener;
  18. import java.beans.PropertyChangeEvent;
  19. import java.io.Serializable;
  20. /**
  21. * The standard column-handler for a <code>JTable</code>.
  22. * <p>
  23. * <strong>Warning:</strong>
  24. * Serialized objects of this class will not be compatible with
  25. * future Swing releases. The current serialization support is appropriate
  26. * for short term storage or RMI between applications running the same
  27. * version of Swing. A future release of Swing will provide support for
  28. * long term persistence.
  29. *
  30. * @version 1.35 02/02/00
  31. * @author Alan Chung
  32. * @author Philip Milne
  33. * @see JTable
  34. */
  35. public class DefaultTableColumnModel implements TableColumnModel,
  36. PropertyChangeListener, ListSelectionListener, Serializable
  37. {
  38. //
  39. // Instance Variables
  40. //
  41. /** Array of TableColumn objects in this model */
  42. protected Vector tableColumns;
  43. /** Model for keeping track of column selections */
  44. protected ListSelectionModel selectionModel;
  45. /** Width margin between each column */
  46. protected int columnMargin;
  47. /** List of TableColumnModelListener */
  48. protected EventListenerList listenerList = new EventListenerList();
  49. /** Change event (only one needed) */
  50. transient protected ChangeEvent changeEvent = null;
  51. /** Column selection allowed in this column model */
  52. protected boolean columnSelectionAllowed;
  53. /** A local cache of the combined width of all columns */
  54. protected int totalColumnWidth;
  55. //
  56. // Constructors
  57. //
  58. /**
  59. * Creates a default table column model.
  60. */
  61. public DefaultTableColumnModel() {
  62. super();
  63. // Initialize local ivars to default
  64. tableColumns = new Vector();
  65. setSelectionModel(createSelectionModel());
  66. getSelectionModel().setAnchorSelectionIndex(0);
  67. setColumnMargin(1);
  68. invalidateWidthCache();
  69. setColumnSelectionAllowed(false);
  70. }
  71. //
  72. // Modifying the model
  73. //
  74. /**
  75. * Appends <code>aColumn</code> to the end of the
  76. * <code>tableColumns</code> array.
  77. * This method also posts the <code>columnAdded</code>
  78. * event to its listeners.
  79. *
  80. * @param column the <code>TableColumn</code> to be added
  81. * @exception IllegalArgumentException if <code>aColumn</code> is
  82. * <code>null</code>
  83. * @see #removeColumn
  84. */
  85. public void addColumn(TableColumn aColumn) {
  86. if (aColumn == null) {
  87. throw new IllegalArgumentException("Object is null");
  88. }
  89. tableColumns.addElement(aColumn);
  90. aColumn.addPropertyChangeListener(this);
  91. invalidateWidthCache();
  92. // Post columnAdded event notification
  93. fireColumnAdded(new TableColumnModelEvent(this, 0,
  94. getColumnCount() - 1));
  95. }
  96. /**
  97. * Deletes the <code>column</code> from the
  98. * <code>tableColumns</code> array. This method will do nothing if
  99. * <code>column</code> is not in the table's columns list.
  100. * <code>tile</code> is called
  101. * to resize both the header and table views.
  102. * This method also posts a <code>columnRemoved</code>
  103. * event to its listeners.
  104. *
  105. * @param column the <code>TableColumn</code> to be removed
  106. * @see #addColumn
  107. */
  108. public void removeColumn(TableColumn column) {
  109. int columnIndex = tableColumns.indexOf(column);
  110. if (columnIndex != -1) {
  111. // Adjust for the selection
  112. if (selectionModel != null)
  113. selectionModel.removeIndexInterval(columnIndex,columnIndex);
  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. getColumnCount() - 1, 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. boolean reselect = false;
  155. if (selectionModel.isSelectedIndex(columnIndex)) {
  156. selectionModel.removeSelectionInterval(columnIndex,columnIndex);
  157. reselect = true;
  158. }
  159. tableColumns.removeElementAt(columnIndex);
  160. tableColumns.insertElementAt(aColumn, newIndex);
  161. if (reselect) {
  162. selectionModel.addSelectionInterval(newIndex, newIndex);
  163. }
  164. // Post columnMoved event notification. (JTable and JTableHeader
  165. // listens so they can adjust size and redraw)
  166. fireColumnMoved(new TableColumnModelEvent(this, columnIndex,
  167. newIndex));
  168. }
  169. /**
  170. * Sets the column margin to <code>newMargin</code>. This method
  171. * also posts a <code>columnMarginChanged</code> event to its
  172. * listeners.
  173. *
  174. * @param newMargin the new margin width, in pixels
  175. * @see #getColumnMargin
  176. * @see #getTotalColumnWidth
  177. */
  178. public void setColumnMargin(int newMargin) {
  179. if (newMargin != columnMargin) {
  180. columnMargin = newMargin;
  181. // Post columnMarginChanged event notification.
  182. fireColumnMarginChanged();
  183. }
  184. }
  185. //
  186. // Querying the model
  187. //
  188. /**
  189. * Returns the number of columns in the <code>tableColumns</code> array.
  190. *
  191. * @return the number of columns in the <code>tableColumns</code> array
  192. * @see #getColumns
  193. */
  194. public int getColumnCount() {
  195. return tableColumns.size();
  196. }
  197. /**
  198. * Returns an <code>Enumeration</code> of all the columns in the model.
  199. * @return an <code>Enumeration</code> of the columns in the model
  200. */
  201. public Enumeration getColumns() {
  202. return tableColumns.elements();
  203. }
  204. /**
  205. * Returns the index of the first column in the <code>tableColumns</code>
  206. * array whose identifier is equal to <code>identifier</code>,
  207. * when compared using <code>equals</code>.
  208. *
  209. * @param identifier the identifier object
  210. * @return the index of the first column in the
  211. * <code>tableColumns</code> array whose identifier
  212. * is equal to <code>identifier</code>
  213. * @exception IllegalArgumentException if <code>identifier</code>
  214. * is <code>null</code>, or if no
  215. * <code>TableColumn</code> has this
  216. * <code>identifier</code>
  217. * @see #getColumn
  218. */
  219. public int getColumnIndex(Object identifier) {
  220. if (identifier == null) {
  221. throw new IllegalArgumentException("Identifier is null");
  222. }
  223. Enumeration enumeration = getColumns();
  224. TableColumn aColumn;
  225. int index = 0;
  226. while (enumeration.hasMoreElements()) {
  227. aColumn = (TableColumn)enumeration.nextElement();
  228. // Compare them this way in case the column's identifier is null.
  229. if (identifier.equals(aColumn.getIdentifier()))
  230. return index;
  231. index++;
  232. }
  233. throw new IllegalArgumentException("Identifier not found");
  234. }
  235. /**
  236. * Returns the <code>TableColumn</code> object for the column
  237. * at <code>columnIndex</code>.
  238. *
  239. * @param columnIndex the index of the column desired
  240. * @return the <code>TableColumn</code> object for the column
  241. * at <code>columnIndex</code>
  242. */
  243. public TableColumn getColumn(int columnIndex) {
  244. return (TableColumn)tableColumns.elementAt(columnIndex);
  245. }
  246. /**
  247. * Returns the width margin for <code>TableColumn</code>.
  248. * The default <code>columnMargin</code> is 1.
  249. *
  250. * @return the maximum width for the <code>TableColumn</code>
  251. * @see #setColumnMargin
  252. */
  253. public int getColumnMargin() {
  254. return columnMargin;
  255. }
  256. /**
  257. * Returns the index of the column that lies at position <code>x</code>,
  258. * or -1 if no column covers this point.
  259. *
  260. * @param x the horizontal location of interest
  261. * @return the index of the column or -1 if no column is found
  262. */
  263. public int getColumnIndexAtX(int x) {
  264. if (x < 0) {
  265. return -1;
  266. }
  267. int cc = getColumnCount();
  268. for(int column = 0; column < cc; column++) {
  269. x = x - getColumn(column).getWidth();
  270. if (x < 0) {
  271. return column;
  272. }
  273. }
  274. return -1;
  275. }
  276. /**
  277. * Returns the total combined width of all columns.
  278. * @return the <code>totalColumnWidth</code> property
  279. */
  280. public int getTotalColumnWidth() {
  281. if (totalColumnWidth == -1) {
  282. recalcWidthCache();
  283. }
  284. return totalColumnWidth;
  285. }
  286. //
  287. // Selection model
  288. //
  289. /**
  290. * Sets the selection model for this <code>TableColumnModel</code>
  291. * to <code>newModel</code>
  292. * and registers for listener notifications from the new selection
  293. * model. If <code>newModel</code> is <code>null</code>,
  294. * an exception is thrown.
  295. *
  296. * @param newModel the new selection model
  297. * @exception IllegalArgumentException if <code>newModel</code>
  298. * is <code>null</code>
  299. * @see #getSelectionModel
  300. */
  301. public void setSelectionModel(ListSelectionModel newModel) {
  302. if (newModel == null) {
  303. throw new IllegalArgumentException("Cannot set a null SelectionModel");
  304. }
  305. ListSelectionModel oldModel = selectionModel;
  306. if (newModel != oldModel) {
  307. if (oldModel != null) {
  308. oldModel.removeListSelectionListener(this);
  309. }
  310. selectionModel= newModel;
  311. if (newModel != null) {
  312. newModel.addListSelectionListener(this);
  313. }
  314. }
  315. }
  316. /**
  317. * Returns the <code>ListSelectionModel</code> that is used to
  318. * maintain column selection state.
  319. *
  320. * @return the object that provides column selection state. Or
  321. * <code>null</code> if row selection is not allowed.
  322. * @see #setSelectionModel
  323. */
  324. public ListSelectionModel getSelectionModel() {
  325. return selectionModel;
  326. }
  327. // implements javax.swing.table.TableColumnModel
  328. /**
  329. * Sets whether column selection is allowed. The default is false.
  330. * @param true if column selection will be allowed, false otherwise
  331. */
  332. public void setColumnSelectionAllowed(boolean flag) {
  333. columnSelectionAllowed = flag;
  334. }
  335. // implements javax.swing.table.TableColumnModel
  336. /**
  337. * Returns true if column selection is allowed, otherwise false.
  338. * The default is false.
  339. * @return the <code>columnSelectionAllowed</code> property
  340. */
  341. public boolean getColumnSelectionAllowed() {
  342. return columnSelectionAllowed;
  343. }
  344. // implements javax.swing.table.TableColumnModel
  345. /**
  346. * Returns an array of selected columns. If <code>selectionModel</code>
  347. * is <code>null</code>, returns an empty array.
  348. * @return an array of selected columns or an empty array if nothing
  349. * is selected or the <code>selectionModel</code> is
  350. * <code>null</code>
  351. */
  352. public int[] getSelectedColumns() {
  353. if (selectionModel != null) {
  354. int iMin = selectionModel.getMinSelectionIndex();
  355. int iMax = selectionModel.getMaxSelectionIndex();
  356. if ((iMin == -1) || (iMax == -1)) {
  357. return new int[0];
  358. }
  359. int[] rvTmp = new int[1+ (iMax - iMin)];
  360. int n = 0;
  361. for(int i = iMin; i <= iMax; i++) {
  362. if (selectionModel.isSelectedIndex(i)) {
  363. rvTmp[n++] = i;
  364. }
  365. }
  366. int[] rv = new int[n];
  367. System.arraycopy(rvTmp, 0, rv, 0, n);
  368. return rv;
  369. }
  370. return new int[0];
  371. }
  372. // implements javax.swing.table.TableColumnModel
  373. /**
  374. * Returns the number of columns selected.
  375. * @return the number of columns selected
  376. */
  377. public int getSelectedColumnCount() {
  378. if (selectionModel != null) {
  379. int iMin = selectionModel.getMinSelectionIndex();
  380. int iMax = selectionModel.getMaxSelectionIndex();
  381. int count = 0;
  382. for(int i = iMin; i <= iMax; i++) {
  383. if (selectionModel.isSelectedIndex(i)) {
  384. count++;
  385. }
  386. }
  387. return count;
  388. }
  389. return 0;
  390. }
  391. //
  392. // Listener Support Methods
  393. //
  394. // implements javax.swing.table.TableColumnModel
  395. /**
  396. * Adds a listener for table column model events.
  397. * @param x a <code>TableColumnModelListener</code> object
  398. */
  399. public void addColumnModelListener(TableColumnModelListener x) {
  400. listenerList.add(TableColumnModelListener.class, x);
  401. }
  402. // implements javax.swing.table.TableColumnModel
  403. /**
  404. * Removes a listener for table column model events.
  405. * @param x a <code>TableColumnModelListener</code> object
  406. */
  407. public void removeColumnModelListener(TableColumnModelListener x) {
  408. listenerList.remove(TableColumnModelListener.class, x);
  409. }
  410. //
  411. // Event firing methods
  412. //
  413. /**
  414. * Notifies all listeners that have registered interest for
  415. * notification on this event type. The event instance
  416. * is lazily created using the parameters passed into
  417. * the fire method.
  418. * @param e the event received
  419. * @see EventListenerList
  420. */
  421. protected void fireColumnAdded(TableColumnModelEvent e) {
  422. // Guaranteed to return a non-null array
  423. Object[] listeners = listenerList.getListenerList();
  424. // Process the listeners last to first, notifying
  425. // those that are interested in this event
  426. for (int i = listeners.length-2; i>=0; i-=2) {
  427. if (listeners[i]==TableColumnModelListener.class) {
  428. // Lazily create the event:
  429. // if (e == null)
  430. // e = new ChangeEvent(this);
  431. ((TableColumnModelListener)listeners[i+1]).
  432. columnAdded(e);
  433. }
  434. }
  435. }
  436. /**
  437. * Notifies all listeners that have registered interest for
  438. * notification on this event type. The event instance
  439. * is lazily created using the parameters passed into
  440. * the fire method.
  441. * @param e the event received
  442. * @see EventListenerList
  443. */
  444. protected void fireColumnRemoved(TableColumnModelEvent e) {
  445. // Guaranteed to return a non-null array
  446. Object[] listeners = listenerList.getListenerList();
  447. // Process the listeners last to first, notifying
  448. // those that are interested in this event
  449. for (int i = listeners.length-2; i>=0; i-=2) {
  450. if (listeners[i]==TableColumnModelListener.class) {
  451. // Lazily create the event:
  452. // if (e == null)
  453. // e = new ChangeEvent(this);
  454. ((TableColumnModelListener)listeners[i+1]).
  455. columnRemoved(e);
  456. }
  457. }
  458. }
  459. /**
  460. * Notifies all listeners that have registered interest for
  461. * notification on this event type. The event instance
  462. * is lazily created using the parameters passed into
  463. * the fire method.
  464. * @param e the event received
  465. * @see EventListenerList
  466. */
  467. protected void fireColumnMoved(TableColumnModelEvent e) {
  468. // Guaranteed to return a non-null array
  469. Object[] listeners = listenerList.getListenerList();
  470. // Process the listeners last to first, notifying
  471. // those that are interested in this event
  472. for (int i = listeners.length-2; i>=0; i-=2) {
  473. if (listeners[i]==TableColumnModelListener.class) {
  474. // Lazily create the event:
  475. // if (e == null)
  476. // e = new ChangeEvent(this);
  477. ((TableColumnModelListener)listeners[i+1]).
  478. columnMoved(e);
  479. }
  480. }
  481. }
  482. /**
  483. * Notifies all listeners that have registered interest for
  484. * notification on this event type. The event instance
  485. * is lazily created using the parameters passed into
  486. * the fire method.
  487. * @param e the event received
  488. * @see EventListenerList
  489. */
  490. protected void fireColumnSelectionChanged(ListSelectionEvent e) {
  491. // Guaranteed to return a non-null array
  492. Object[] listeners = listenerList.getListenerList();
  493. // Process the listeners last to first, notifying
  494. // those that are interested in this event
  495. for (int i = listeners.length-2; i>=0; i-=2) {
  496. if (listeners[i]==TableColumnModelListener.class) {
  497. // Lazily create the event:
  498. // if (e == null)
  499. // e = new ChangeEvent(this);
  500. ((TableColumnModelListener)listeners[i+1]).
  501. columnSelectionChanged(e);
  502. }
  503. }
  504. }
  505. /**
  506. * Notifies all listeners that have registered interest for
  507. * notification on this event type. The event instance
  508. * is lazily created using the parameters passed into
  509. * the fire method.
  510. * @param e the event received
  511. * @see EventListenerList
  512. */
  513. protected void fireColumnMarginChanged() {
  514. // Guaranteed to return a non-null array
  515. Object[] listeners = listenerList.getListenerList();
  516. // Process the listeners last to first, notifying
  517. // those that are interested in this event
  518. for (int i = listeners.length-2; i>=0; i-=2) {
  519. if (listeners[i]==TableColumnModelListener.class) {
  520. // Lazily create the event:
  521. if (changeEvent == null)
  522. changeEvent = new ChangeEvent(this);
  523. ((TableColumnModelListener)listeners[i+1]).
  524. columnMarginChanged(changeEvent);
  525. }
  526. }
  527. }
  528. /**
  529. * Returns an array of all the listeners of the given type that
  530. * were added to this model.
  531. *
  532. * @param listenerType the listener class to match
  533. * @return all of the objects receiving <code>listenerType</code>
  534. * notifications from this model
  535. *
  536. * @since 1.3
  537. */
  538. public EventListener[] getListeners(Class listenerType) {
  539. return listenerList.getListeners(listenerType);
  540. }
  541. //
  542. // Implementing the PropertyChangeListener interface
  543. //
  544. // PENDING(alan)
  545. // implements java.beans.PropertyChangeListener
  546. /**
  547. * Property Change Listener change method. Used to track changes
  548. * to the column width or preferred column width.
  549. *
  550. * @param evt <code>PropertyChangeEvent</code>
  551. */
  552. public void propertyChange(PropertyChangeEvent evt) {
  553. String name = evt.getPropertyName();
  554. if (name == "width" || name == "preferredWidth") {
  555. invalidateWidthCache();
  556. // This is a misnomer, we're using this method
  557. // simply to cause a relayout.
  558. fireColumnMarginChanged();
  559. }
  560. }
  561. //
  562. // Implementing ListSelectionListener interface
  563. //
  564. // implements javax.swing.event.ListSelectionListener
  565. /**
  566. * A <code>ListSelectionListener</code> that forwards
  567. * <code>ListSelectionEvents</code> when there is a column
  568. * selection change.
  569. *
  570. * @param e the change event
  571. */
  572. public void valueChanged(ListSelectionEvent e) {
  573. fireColumnSelectionChanged(e);
  574. }
  575. //
  576. // Protected Methods
  577. //
  578. /**
  579. * Creates a new default list selection model.
  580. */
  581. protected ListSelectionModel createSelectionModel() {
  582. return new DefaultListSelectionModel();
  583. }
  584. /**
  585. * Recalculates the total combined width of all columns. Updates the
  586. * <code>totalColumnWidth</code> property.
  587. */
  588. protected void recalcWidthCache() {
  589. Enumeration enumeration = getColumns();
  590. totalColumnWidth = 0;
  591. while (enumeration.hasMoreElements()) {
  592. totalColumnWidth += ((TableColumn)enumeration.nextElement()).getWidth();
  593. }
  594. }
  595. private void invalidateWidthCache() {
  596. totalColumnWidth = -1;
  597. }
  598. } // End of class DefaultTableColumnModel