1. /*
  2. * @(#)DefaultTableColumnModel.java 1.27 01/11/29
  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.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.beans.PropertyChangeListener;
  14. import java.beans.PropertyChangeEvent;
  15. import java.io.Serializable;
  16. /**
  17. * The standard column-handler for a JTable.
  18. * <p>
  19. * <strong>Warning:</strong>
  20. * Serialized objects of this class will not be compatible with
  21. * future Swing releases. The current serialization support is appropriate
  22. * for short term storage or RMI between applications running the same
  23. * version of Swing. A future release of Swing will provide support for
  24. * long term persistence.
  25. *
  26. * @version 1.27 11/29/01
  27. * @author Alan Chung
  28. * @author Philip Milne
  29. * @see JTable
  30. */
  31. public class DefaultTableColumnModel implements TableColumnModel,
  32. PropertyChangeListener, ListSelectionListener, Serializable
  33. {
  34. //
  35. // Instance Variables
  36. //
  37. /** Array of TableColumn objects in this model */
  38. protected Vector tableColumns;
  39. /** Model for keeping track of column selections */
  40. protected ListSelectionModel selectionModel;
  41. /** Width margin between each column */
  42. protected int columnMargin;
  43. /** List of TableColumnModelListener */
  44. protected EventListenerList listenerList = new EventListenerList();
  45. /** Change event (only one needed) */
  46. transient protected ChangeEvent changeEvent = null;
  47. /** Column selection allowed in this column model */
  48. protected boolean columnSelectionAllowed;
  49. /** A local cache of the combined width of all columns */
  50. protected int totalColumnWidth;
  51. //
  52. // Constructors
  53. //
  54. /**
  55. * Creates a default table column model.
  56. */
  57. public DefaultTableColumnModel() {
  58. super();
  59. // Initialize local ivars to default
  60. tableColumns = new Vector();
  61. setSelectionModel(createSelectionModel());
  62. setColumnMargin(1);
  63. setColumnSelectionAllowed(false);
  64. }
  65. //
  66. // Modifying the model
  67. //
  68. /**
  69. * Appends <I>aColumn</I> to the end of the receiver's tableColumns array.
  70. * This method also posts the columnAdded() event to its listeners.
  71. *
  72. * @param column The <B>TableColumn</B> to be added
  73. * @exception IllegalArgumentException if <I>aColumn</I> is null
  74. * @see #removeColumn
  75. */
  76. public void addColumn(TableColumn aColumn) {
  77. if (aColumn == null) {
  78. throw new IllegalArgumentException("Object is null");
  79. }
  80. tableColumns.addElement(aColumn);
  81. aColumn.addPropertyChangeListener(this);
  82. recalcWidthCache();
  83. // Post columnAdded event notification
  84. fireColumnAdded(new TableColumnModelEvent(this, 0,
  85. getColumnCount() - 1));
  86. }
  87. /**
  88. * Deletes the <B>TableColumn</B> <I>column</I> from the
  89. * receiver's table columns array. This method will do nothing if
  90. * <I>column</I> is not in the table's columns list. tile() is called
  91. * to resize both the header and table views.
  92. * This method also posts the columnRemoved() event to its listeners.
  93. *
  94. * @param column The <B>TableColumn</B> to be removed
  95. * @see #addColumn
  96. */
  97. public void removeColumn(TableColumn column) {
  98. int columnIndex = tableColumns.indexOf(column);
  99. if (columnIndex != -1) {
  100. // Adjust for the selection
  101. if (selectionModel != null)
  102. selectionModel.removeIndexInterval(columnIndex,columnIndex);
  103. column.removePropertyChangeListener(this);
  104. tableColumns.removeElementAt(columnIndex);
  105. recalcWidthCache();
  106. // Post columnAdded event notification. (JTable and JTableHeader
  107. // listens so they can adjust size and redraw)
  108. fireColumnRemoved(new TableColumnModelEvent(this,
  109. getColumnCount() - 1, 0));
  110. }
  111. }
  112. /**
  113. * Moves the column and heading at <I>columnIndex</I> to <I>newIndex</I>.
  114. * The old column at <I>columnIndex</I> will now be found at <I>newIndex</I>,
  115. * The column that used to be at <I>newIndex</I> is shifted left or right
  116. * to make room.
  117. * This will not move any columns if <I>columnIndex</I> equals <I>newIndex</I>.
  118. * This method also posts the columnMoved() event to its listeners.
  119. *
  120. * @param columnIndex the index of column to be moved
  121. * @param newIndex New index to move the column
  122. * @exception IllegalArgumentException if <I>column</I> or
  123. * <I>newIndex</I>
  124. * are not in the valid range
  125. */
  126. public void moveColumn(int columnIndex, int newIndex) {
  127. if ((columnIndex < 0) || (columnIndex >= getColumnCount()) ||
  128. (newIndex < 0) || (newIndex >= getColumnCount()))
  129. throw new IllegalArgumentException("moveColumn() - Index out of range");
  130. TableColumn aColumn;
  131. // Do nothing if the parameters will result in a no-op move
  132. if (columnIndex == newIndex)
  133. return;
  134. aColumn = (TableColumn)tableColumns.elementAt(columnIndex);
  135. boolean reselect = false;
  136. if (selectionModel.isSelectedIndex(columnIndex)) {
  137. selectionModel.removeSelectionInterval(columnIndex,columnIndex);
  138. reselect = true;
  139. }
  140. tableColumns.removeElementAt(columnIndex);
  141. tableColumns.insertElementAt(aColumn, newIndex);
  142. if (reselect)
  143. selectionModel.addSelectionInterval(newIndex, newIndex);
  144. // Post columnMoved event notification. (JTable and JTableHeader
  145. // listens so they can adjust size and redraw)
  146. fireColumnMoved(new TableColumnModelEvent(this, columnIndex,
  147. newIndex));
  148. }
  149. /**
  150. * Sets the column margin to <I>newMargin</I>.
  151. * This method also posts the columnMarginChanged() event to its
  152. * listeners.
  153. *
  154. * @param newMargin the width margin of the column
  155. * @see #getColumnMargin
  156. * @see #getTotalColumnWidth
  157. */
  158. public void setColumnMargin(int newMargin) {
  159. if (newMargin != columnMargin) {
  160. columnMargin = newMargin;
  161. recalcWidthCache();
  162. // Post columnMarginChanged event notification.
  163. fireColumnMarginChanged();
  164. }
  165. }
  166. //
  167. // Querying the model
  168. //
  169. /**
  170. * Returns the number of columns in the receiver's table columns array.
  171. *
  172. * @return the number of columns in the receiver's table columns array
  173. * @see #getColumns
  174. */
  175. public int getColumnCount() {
  176. return tableColumns.size();
  177. }
  178. /**
  179. * Returns an Enumeration of all the columns in the model
  180. */
  181. public Enumeration getColumns() {
  182. return tableColumns.elements();
  183. }
  184. /**
  185. * Returns the index of the first column in the receiver's
  186. * columns array whose identifier is equal to <I>identifier</I>,
  187. * when compared using <I>equals()</I>.
  188. *
  189. * @return the index of the first table column in the receiver's
  190. * tableColumns array whose identifier is equal to
  191. * <I>identifier</I>, when compared using equals().
  192. * @param identifier the identifier object
  193. * @exception IllegalArgumentException if <I>identifier</I> is null or no TableColumn has this identifier
  194. * @see #getColumn
  195. */
  196. public int getColumnIndex(Object identifier) {
  197. if (identifier == null) {
  198. throw new IllegalArgumentException("Identifier is null");
  199. }
  200. Enumeration enumeration = getColumns();
  201. TableColumn aColumn;
  202. int index = 0;
  203. while (enumeration.hasMoreElements()) {
  204. aColumn = (TableColumn)enumeration.nextElement();
  205. // Compare them this way in case the column's identifier is null.
  206. if (identifier.equals(aColumn.getIdentifier()))
  207. return index;
  208. index++;
  209. }
  210. throw new IllegalArgumentException("Identifier not found");
  211. }
  212. /**
  213. * Returns the <B>TableColumn</B> object for the column at <I>columnIndex</I>
  214. *
  215. * @return the TableColumn object for the column at <I>columnIndex</I>
  216. * @param columnIndex the index of the column desired
  217. */
  218. public TableColumn getColumn(int columnIndex) {
  219. return (TableColumn)tableColumns.elementAt(columnIndex);
  220. }
  221. /**
  222. * Returns the width margin for <B>TableColumn</B>.
  223. * The default columnMargin is 1.
  224. *
  225. * @return the maximum width for the <B>TableColumn</B>.
  226. * @see #setColumnMargin
  227. */
  228. public int getColumnMargin()
  229. {
  230. return columnMargin;
  231. }
  232. /**
  233. * Returns the index of the column that lies on the <I>xPosition</I>,
  234. * or -1 if it lies outside the any of the column's bounds.
  235. *
  236. * @return the index of the column or -1 if no column is found
  237. */
  238. public int getColumnIndexAtX(int xPosition) {
  239. int index = 0;
  240. Point aPoint = new Point(xPosition, 1);
  241. Rectangle columnRect = new Rectangle(0,0,0,3);
  242. Enumeration enumeration = getColumns();
  243. while (enumeration.hasMoreElements()) {
  244. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  245. columnRect.width = aColumn.getWidth() + columnMargin;
  246. if (columnRect.contains(aPoint))
  247. return index;
  248. columnRect.x += columnRect.width;
  249. index++;
  250. }
  251. return -1;
  252. }
  253. // implements javax.swing.table.TableColumnModel
  254. public int getTotalColumnWidth() {
  255. return totalColumnWidth;
  256. }
  257. //
  258. // Selection model
  259. //
  260. /**
  261. * Sets the selection model for this TableColumnModel to <I>newModel</I>
  262. * and registers with for listner notifications from the new selection
  263. * model. If <I>newModel</I> is null, it means columns are not
  264. * selectable.
  265. *
  266. * @param newModel the new selection model
  267. * @exception IllegalArgumentException if <I>newModel</I> is null
  268. * @see #getSelectionModel
  269. */
  270. public void setSelectionModel(ListSelectionModel newModel) {
  271. if (newModel == null) {
  272. throw new IllegalArgumentException("Cannot set a null SelectionModel");
  273. }
  274. ListSelectionModel oldModel = selectionModel;
  275. if (newModel != oldModel) {
  276. if (oldModel != null) {
  277. oldModel.removeListSelectionListener(this);
  278. }
  279. selectionModel= newModel;
  280. if (newModel != null) {
  281. newModel.addListSelectionListener(this);
  282. }
  283. }
  284. }
  285. /**
  286. * Returns the <B>ListSelectionModel</B> that is used to maintain column
  287. * selection state.
  288. *
  289. * @return the object that provides column selection state. Or
  290. * <B>null</B> if row selection is not allowed.
  291. * @see #setSelectionModel()
  292. */
  293. public ListSelectionModel getSelectionModel() {
  294. return selectionModel;
  295. }
  296. // implements javax.swing.table.TableColumnModel
  297. public void setColumnSelectionAllowed(boolean flag) {
  298. columnSelectionAllowed = flag;
  299. }
  300. // implements javax.swing.table.TableColumnModel
  301. public boolean getColumnSelectionAllowed() {
  302. return columnSelectionAllowed;
  303. }
  304. // implements javax.swing.table.TableColumnModel
  305. public int[] getSelectedColumns() {
  306. if (selectionModel != null) {
  307. int iMin = selectionModel.getMinSelectionIndex();
  308. int iMax = selectionModel.getMaxSelectionIndex();
  309. if ((iMin == -1) || (iMax == -1)) {
  310. return new int[0];
  311. }
  312. int[] rvTmp = new int[1+ (iMax - iMin)];
  313. int n = 0;
  314. for(int i = iMin; i <= iMax; i++) {
  315. if (selectionModel.isSelectedIndex(i)) {
  316. rvTmp[n++] = i;
  317. }
  318. }
  319. int[] rv = new int[n];
  320. System.arraycopy(rvTmp, 0, rv, 0, n);
  321. return rv;
  322. }
  323. return new int[0];
  324. }
  325. // implements javax.swing.table.TableColumnModel
  326. public int getSelectedColumnCount() {
  327. if (selectionModel != null) {
  328. int iMin = selectionModel.getMinSelectionIndex();
  329. int iMax = selectionModel.getMaxSelectionIndex();
  330. int count = 0;
  331. for(int i = iMin; i <= iMax; i++) {
  332. if (selectionModel.isSelectedIndex(i)) {
  333. count++;
  334. }
  335. }
  336. return count;
  337. }
  338. return 0;
  339. }
  340. //
  341. // Listener Support Methods
  342. //
  343. // implements javax.swing.table.TableColumnModel
  344. public void addColumnModelListener(TableColumnModelListener x) {
  345. listenerList.add(TableColumnModelListener.class, x);
  346. }
  347. // implements javax.swing.table.TableColumnModel
  348. public void removeColumnModelListener(TableColumnModelListener x) {
  349. listenerList.remove(TableColumnModelListener.class, x);
  350. }
  351. //
  352. // Event firing methods
  353. //
  354. /*
  355. * Notify all listeners that have registered interest for
  356. * notification on this event type. The event instance
  357. * is lazily created using the parameters passed into
  358. * the fire method.
  359. * @see EventListenerList
  360. */
  361. protected void fireColumnAdded(TableColumnModelEvent e) {
  362. // Guaranteed to return a non-null array
  363. Object[] listeners = listenerList.getListenerList();
  364. // Process the listeners last to first, notifying
  365. // those that are interested in this event
  366. for (int i = listeners.length-2; i>=0; i-=2) {
  367. if (listeners[i]==TableColumnModelListener.class) {
  368. // Lazily create the event:
  369. // if (e == null)
  370. // e = new ChangeEvent(this);
  371. ((TableColumnModelListener)listeners[i+1]).
  372. columnAdded(e);
  373. }
  374. }
  375. }
  376. /*
  377. * Notify all listeners that have registered interest for
  378. * notification on this event type. The event instance
  379. * is lazily created using the parameters passed into
  380. * the fire method.
  381. * @see EventListenerList
  382. */
  383. protected void fireColumnRemoved(TableColumnModelEvent e) {
  384. // Guaranteed to return a non-null array
  385. Object[] listeners = listenerList.getListenerList();
  386. // Process the listeners last to first, notifying
  387. // those that are interested in this event
  388. for (int i = listeners.length-2; i>=0; i-=2) {
  389. if (listeners[i]==TableColumnModelListener.class) {
  390. // Lazily create the event:
  391. // if (e == null)
  392. // e = new ChangeEvent(this);
  393. ((TableColumnModelListener)listeners[i+1]).
  394. columnRemoved(e);
  395. }
  396. }
  397. }
  398. /*
  399. * Notify all listeners that have registered interest for
  400. * notification on this event type. The event instance
  401. * is lazily created using the parameters passed into
  402. * the fire method.
  403. * @see EventListenerList
  404. */
  405. protected void fireColumnMoved(TableColumnModelEvent e) {
  406. // Guaranteed to return a non-null array
  407. Object[] listeners = listenerList.getListenerList();
  408. // Process the listeners last to first, notifying
  409. // those that are interested in this event
  410. for (int i = listeners.length-2; i>=0; i-=2) {
  411. if (listeners[i]==TableColumnModelListener.class) {
  412. // Lazily create the event:
  413. // if (e == null)
  414. // e = new ChangeEvent(this);
  415. ((TableColumnModelListener)listeners[i+1]).
  416. columnMoved(e);
  417. }
  418. }
  419. }
  420. /*
  421. * Notify all listeners that have registered interest for
  422. * notification on this event type. The event instance
  423. * is lazily created using the parameters passed into
  424. * the fire method.
  425. * @see EventListenerList
  426. */
  427. protected void fireColumnSelectionChanged(ListSelectionEvent e) {
  428. // Guaranteed to return a non-null array
  429. Object[] listeners = listenerList.getListenerList();
  430. // Process the listeners last to first, notifying
  431. // those that are interested in this event
  432. for (int i = listeners.length-2; i>=0; i-=2) {
  433. if (listeners[i]==TableColumnModelListener.class) {
  434. // Lazily create the event:
  435. // if (e == null)
  436. // e = new ChangeEvent(this);
  437. ((TableColumnModelListener)listeners[i+1]).
  438. columnSelectionChanged(e);
  439. }
  440. }
  441. }
  442. /*
  443. * Notify all listeners that have registered interest for
  444. * notification on this event type. The event instance
  445. * is lazily created using the parameters passed into
  446. * the fire method.
  447. * @see EventListenerList
  448. */
  449. protected void fireColumnMarginChanged() {
  450. // Guaranteed to return a non-null array
  451. Object[] listeners = listenerList.getListenerList();
  452. // Process the listeners last to first, notifying
  453. // those that are interested in this event
  454. for (int i = listeners.length-2; i>=0; i-=2) {
  455. if (listeners[i]==TableColumnModelListener.class) {
  456. // Lazily create the event:
  457. if (changeEvent == null)
  458. changeEvent = new ChangeEvent(this);
  459. ((TableColumnModelListener)listeners[i+1]).
  460. columnMarginChanged(changeEvent);
  461. }
  462. }
  463. }
  464. //
  465. // Implementing the PropertyChangeListener interface
  466. //
  467. // PENDING(alan)
  468. // implements java.beans.PropertyChangeListener
  469. public void propertyChange(PropertyChangeEvent evt) {
  470. String name = evt.getPropertyName();
  471. if (TableColumn.COLUMN_WIDTH_PROPERTY.equals(name)) {
  472. recalcWidthCache();
  473. }
  474. else if (TableColumn.HEADER_VALUE_PROPERTY.equals(name) ||
  475. TableColumn.HEADER_RENDERER_PROPERTY.equals(name)) {
  476. }
  477. else if (TableColumn.CELL_RENDERER_PROPERTY.equals(name)) {
  478. }
  479. }
  480. //
  481. // Implementing ListSelectionListener interface
  482. //
  483. // implements javax.swing.event.ListSelectionListener
  484. public void valueChanged(ListSelectionEvent e) {
  485. fireColumnSelectionChanged(e);
  486. }
  487. //
  488. // Protected Methods
  489. //
  490. protected ListSelectionModel createSelectionModel() {
  491. return new DefaultListSelectionModel();
  492. }
  493. protected void recalcWidthCache() {
  494. Enumeration enumeration = getColumns();
  495. totalColumnWidth = 0;
  496. while (enumeration.hasMoreElements()) {
  497. totalColumnWidth += ((TableColumn)enumeration.nextElement()).getWidth() +
  498. columnMargin;
  499. }
  500. }
  501. } // End of class DefaultTableColumnModel