1 /* DefaultTableColumnModel.java --
2 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax.swing.table;
41 import java.beans.PropertyChangeEvent;
42 import java.beans.PropertyChangeListener;
43 import java.io.Serializable;
44 import java.util.Enumeration;
45 import java.util.EventListener;
46 import java.util.Vector;
48 import javax.swing.DefaultListSelectionModel;
49 import javax.swing.JTable;
50 import javax.swing.ListSelectionModel;
51 import javax.swing.event.ChangeEvent;
52 import javax.swing.event.EventListenerList;
53 import javax.swing.event.ListSelectionEvent;
54 import javax.swing.event.ListSelectionListener;
55 import javax.swing.event.TableColumnModelEvent;
56 import javax.swing.event.TableColumnModelListener;
59 * A model that stores information about the columns used in a {@link JTable}.
61 * @see JTable#setColumnModel(TableColumnModel)
63 * @author Andrew Selkirk
65 public class DefaultTableColumnModel
66 implements TableColumnModel, PropertyChangeListener, ListSelectionListener,
69 private static final long serialVersionUID = 6580012493508960512L;
72 * Storage for the table columns.
74 protected Vector tableColumns;
77 * A selection model that keeps track of column selections.
79 protected ListSelectionModel selectionModel;
82 * The space between the columns (the default value is <code>1</code>).
84 protected int columnMargin;
87 * Storage for the listeners registered with the model.
89 protected EventListenerList listenerList = new EventListenerList();
92 * A change event used when notifying listeners of a change to the
93 * <code>columnMargin</code> field. This single event is reused for all
96 // FIXME: use lazy instantiation
97 protected transient ChangeEvent changeEvent = new ChangeEvent(this);
100 * A flag that indicates whether or not columns can be selected.
102 protected boolean columnSelectionAllowed;
105 * The total width of all the columns in this model.
107 protected int totalColumnWidth;
110 * Creates a new table column model with zero columns. A default column
111 * selection model is created by calling {@link #createSelectionModel()}.
112 * The default value for <code>columnMargin</code> is <code>1</code> and
113 * the default value for <code>columnSelectionAllowed</code> is
114 * <code>false</code>.
116 public DefaultTableColumnModel()
118 tableColumns = new Vector();
119 selectionModel = createSelectionModel();
120 selectionModel.addListSelectionListener(this);
122 columnSelectionAllowed = false;
126 * Adds a column to the model then calls
127 * {@link #fireColumnAdded(TableColumnModelEvent)} to notify the registered
128 * listeners. The model registers itself with the column as a
129 * {@link PropertyChangeListener} so that changes to the column width will
130 * invalidate the cached {@link #totalColumnWidth} value.
132 * @param column the column (<code>null</code> not permitted).
134 * @throws IllegalArgumentException if <code>column</code> is
137 * @see #removeColumn(TableColumn)
139 public void addColumn(TableColumn column)
142 throw new IllegalArgumentException("Null 'col' argument.");
143 tableColumns.add(column);
144 column.addPropertyChangeListener(this);
145 invalidateWidthCache();
146 fireColumnAdded(new TableColumnModelEvent(this, 0,
147 tableColumns.size() - 1));
151 * Removes a column from the model then calls
152 * {@link #fireColumnRemoved(TableColumnModelEvent)} to notify the registered
153 * listeners. If the specified column does not belong to the model, or is
154 * <code>null</code>, this method does nothing.
156 * @param column the column to be removed (<code>null</code> permitted).
158 * @see #addColumn(TableColumn)
160 public void removeColumn(TableColumn column)
162 int index = this.tableColumns.indexOf(column);
165 tableColumns.remove(column);
166 fireColumnRemoved(new TableColumnModelEvent(this, index, 0));
167 column.removePropertyChangeListener(this);
168 invalidateWidthCache();
172 * Moves the column at index i to the position specified by index j, then
173 * calls {@link #fireColumnMoved(TableColumnModelEvent)} to notify registered
176 * @param i index of the column that will be moved.
177 * @param j index of the column's new location.
179 * @throws IllegalArgumentException if <code>i</code> or <code>j</code> are
180 * outside the range <code>0</code> to <code>N-1</code>, where
181 * <code>N</code> is the column count.
183 public void moveColumn(int i, int j)
185 int columnCount = getColumnCount();
186 if (i < 0 || i >= columnCount)
187 throw new IllegalArgumentException("Index 'i' out of range.");
188 if (j < 0 || j >= columnCount)
189 throw new IllegalArgumentException("Index 'j' out of range.");
190 Object column = tableColumns.remove(i);
191 tableColumns.add(j, column);
192 fireColumnMoved(new TableColumnModelEvent(this, i, j));
196 * Sets the column margin then calls {@link #fireColumnMarginChanged()} to
197 * notify the registered listeners.
199 * @param margin the column margin.
201 * @see #getColumnMargin()
203 public void setColumnMargin(int margin)
205 columnMargin = margin;
206 fireColumnMarginChanged();
210 * Returns the number of columns in the model.
212 * @return The column count.
214 public int getColumnCount()
216 return tableColumns.size();
220 * Returns an enumeration of the columns in the model.
222 * @return An enumeration of the columns in the model.
224 public Enumeration getColumns()
226 return tableColumns.elements();
230 * Returns the index of the {@link TableColumn} with the given identifier.
232 * @param identifier the identifier (<code>null</code> not permitted).
234 * @return The index of the {@link TableColumn} with the given identifier.
236 * @throws IllegalArgumentException if <code>identifier</code> is
237 * <code>null</code> or there is no column with that identifier.
239 public int getColumnIndex(Object identifier)
241 if (identifier == null)
242 throw new IllegalArgumentException("Null identifier.");
243 int columnCount = tableColumns.size();
244 for (int i = 0; i < columnCount; i++)
246 TableColumn tc = (TableColumn) tableColumns.get(i);
247 if (identifier.equals(tc.getIdentifier()))
250 throw new IllegalArgumentException("No TableColumn with that identifier.");
254 * Returns the column at the specified index.
256 * @param columnIndex the column index (in the range from <code>0</code> to
257 * <code>N-1</code>, where <code>N</code> is the number of columns in
260 * @return The column at the specified index.
262 * @throws ArrayIndexOutOfBoundsException if <code>i</code> is not within
263 * the specified range.
265 public TableColumn getColumn(int columnIndex)
267 return (TableColumn) tableColumns.get(columnIndex);
271 * Returns the column margin.
273 * @return The column margin.
275 * @see #setColumnMargin(int)
277 public int getColumnMargin()
283 * Returns the index of the column that contains the specified x-coordinate.
284 * This method assumes that:
286 * <li>column zero begins at position zero;</li>
287 * <li>all columns appear in order;</li>
288 * <li>individual column widths are taken into account, but the column margin
291 * If no column contains the specified position, this method returns
294 * @param x the x-position.
296 * @return The column index, or <code>-1</code>.
298 public int getColumnIndexAtX(int x)
300 for (int i = 0; i < tableColumns.size(); ++i)
302 int w = ((TableColumn) tableColumns.get(i)).getWidth();
312 * Returns total width of all the columns in the model, ignoring the
313 * {@link #columnMargin}.
315 * @return The total width of all the columns.
317 public int getTotalColumnWidth()
319 if (totalColumnWidth == -1)
321 return totalColumnWidth;
325 * Sets the selection model that will be used to keep track of the selected
328 * @param model the selection model (<code>null</code> not permitted).
330 * @throws IllegalArgumentException if <code>model</code> is
333 * @see #getSelectionModel()
335 public void setSelectionModel(ListSelectionModel model)
338 throw new IllegalArgumentException();
340 selectionModel.removeListSelectionListener(this);
341 selectionModel = model;
342 selectionModel.addListSelectionListener(this);
346 * Returns the selection model used to track table column selections.
348 * @return The selection model.
350 * @see #setSelectionModel(ListSelectionModel)
352 public ListSelectionModel getSelectionModel()
354 return selectionModel;
358 * Sets the flag that indicates whether or not column selection is allowed.
360 * @param flag the new flag value.
362 * @see #getColumnSelectionAllowed()
364 public void setColumnSelectionAllowed(boolean flag)
366 columnSelectionAllowed = flag;
370 * Returns <code>true</code> if column selection is allowed, and
371 * <code>false</code> if column selection is not allowed.
375 * @see #setColumnSelectionAllowed(boolean)
377 public boolean getColumnSelectionAllowed()
379 return columnSelectionAllowed;
383 * Returns an array containing the indices of the selected columns.
385 * @return An array containing the indices of the selected columns.
387 public int[] getSelectedColumns()
389 // FIXME: Implementation of this method was taken from private method
390 // JTable.getSelections(), which is used in various places in JTable
391 // including selected row calculations and cannot be simply removed.
392 // This design should be improved to illuminate duplication of code.
394 ListSelectionModel lsm = this.selectionModel;
395 int sz = getSelectedColumnCount();
396 int [] ret = new int[sz];
398 int lo = lsm.getMinSelectionIndex();
399 int hi = lsm.getMaxSelectionIndex();
401 java.util.ArrayList ls = new java.util.ArrayList();
402 if (lo != -1 && hi != -1)
404 switch (lsm.getSelectionMode())
406 case ListSelectionModel.SINGLE_SELECTION:
410 case ListSelectionModel.SINGLE_INTERVAL_SELECTION:
411 for (int i = lo; i <= hi; ++i)
415 case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:
416 for (int i = lo; i <= hi; ++i)
417 if (lsm.isSelectedIndex(i))
426 * Returns the number of selected columns in the model.
428 * @return The selected column count.
430 * @see #getSelectionModel()
432 public int getSelectedColumnCount()
434 // FIXME: Implementation of this method was taken from private method
435 // JTable.countSelections(), which is used in various places in JTable
436 // including selected row calculations and cannot be simply removed.
437 // This design should be improved to illuminate duplication of code.
439 ListSelectionModel lsm = this.selectionModel;
440 int lo = lsm.getMinSelectionIndex();
441 int hi = lsm.getMaxSelectionIndex();
444 if (lo != -1 && hi != -1)
446 switch (lsm.getSelectionMode())
448 case ListSelectionModel.SINGLE_SELECTION:
452 case ListSelectionModel.SINGLE_INTERVAL_SELECTION:
456 case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:
457 for (int i = lo; i <= hi; ++i)
458 if (lsm.isSelectedIndex(i))
468 * Registers a listener with the model, so that it will receive
469 * {@link TableColumnModelEvent} notifications.
471 * @param listener the listener (<code>null</code> ignored).
473 public void addColumnModelListener(TableColumnModelListener listener)
475 listenerList.add(TableColumnModelListener.class, listener);
479 * Deregisters a listener so that it no longer receives notification of
480 * changes to this model.
482 * @param listener the listener to remove
484 public void removeColumnModelListener(TableColumnModelListener listener)
486 listenerList.remove(TableColumnModelListener.class, listener);
490 * Returns an array containing the listeners that are registered with the
491 * model. If there are no listeners, an empty array is returned.
493 * @return An array containing the listeners that are registered with the
496 * @see #addColumnModelListener(TableColumnModelListener)
499 public TableColumnModelListener[] getColumnModelListeners()
501 return (TableColumnModelListener[])
502 listenerList.getListeners(TableColumnModelListener.class);
506 * Sends the specified {@link TableColumnModelEvent} to all registered
507 * listeners, to indicate that a column has been added to the model. The
508 * event's <code>toIndex</code> attribute should contain the index of the
511 * @param e the event.
513 * @see #addColumn(TableColumn)
515 protected void fireColumnAdded(TableColumnModelEvent e)
517 TableColumnModelListener[] listeners = getColumnModelListeners();
519 for (int i = 0; i < listeners.length; i++)
520 listeners[i].columnAdded(e);
524 * Sends the specified {@link TableColumnModelEvent} to all registered
525 * listeners, to indicate that a column has been removed from the model. The
526 * event's <code>fromIndex</code> attribute should contain the index of the
529 * @param e the event.
531 * @see #removeColumn(TableColumn)
533 protected void fireColumnRemoved(TableColumnModelEvent e)
535 TableColumnModelListener[] listeners = getColumnModelListeners();
537 for (int i = 0; i < listeners.length; i++)
538 listeners[i].columnRemoved(e);
542 * Sends the specified {@link TableColumnModelEvent} to all registered
543 * listeners, to indicate that a column in the model has been moved. The
544 * event's <code>fromIndex</code> attribute should contain the old column
545 * index, and the <code>toIndex</code> attribute should contain the new
548 * @param e the event.
550 * @see #moveColumn(int, int)
552 protected void fireColumnMoved(TableColumnModelEvent e)
554 TableColumnModelListener[] listeners = getColumnModelListeners();
556 for (int i = 0; i < listeners.length; i++)
557 listeners[i].columnMoved(e);
561 * Sends the specified {@link ListSelectionEvent} to all registered listeners,
562 * to indicate that the column selections have changed.
564 * @param e the event.
566 * @see #valueChanged(ListSelectionEvent)
568 protected void fireColumnSelectionChanged(ListSelectionEvent e)
570 EventListener [] listeners = getListeners(TableColumnModelListener.class);
571 for (int i = 0; i < listeners.length; ++i)
572 ((TableColumnModelListener) listeners[i]).columnSelectionChanged(e);
576 * Sends a {@link ChangeEvent} to the model's registered listeners to
577 * indicate that the column margin was changed.
579 * @see #setColumnMargin(int)
581 protected void fireColumnMarginChanged()
583 EventListener [] listeners = getListeners(TableColumnModelListener.class);
584 for (int i = 0; i < listeners.length; ++i)
585 ((TableColumnModelListener) listeners[i]).columnMarginChanged(changeEvent);
589 * Returns an array containing the listeners (of the specified type) that
590 * are registered with this model.
592 * @param listenerType the listener type (must indicate a subclass of
593 * {@link EventListener}, <code>null</code> not permitted).
595 * @return An array containing the listeners (of the specified type) that
596 * are registered with this model.
598 public EventListener[] getListeners(Class listenerType)
600 return listenerList.getListeners(listenerType);
604 * Receives notification of property changes for the columns in the model.
605 * If the <code>width</code> property for any column changes, we invalidate
606 * the {@link #totalColumnWidth} value here.
608 * @param event the event.
610 public void propertyChange(PropertyChangeEvent event)
612 if (event.getPropertyName().equals("width"))
613 invalidateWidthCache();
617 * Receives notification of the change to the list selection model, and
618 * responds by calling
619 * {@link #fireColumnSelectionChanged(ListSelectionEvent)}.
621 * @param e the list selection event.
623 * @see #getSelectionModel()
625 public void valueChanged(ListSelectionEvent e)
627 fireColumnSelectionChanged(e);
631 * Creates a default selection model to track the currently selected
632 * column(s). This method is called by the constructor and returns a new
633 * instance of {@link DefaultListSelectionModel}.
635 * @return A new default column selection model.
637 protected ListSelectionModel createSelectionModel()
639 return new DefaultListSelectionModel();
643 * Recalculates the total width of the columns, if the cached value is
644 * <code>-1</code>. Otherwise this method does nothing.
646 * @see #getTotalColumnWidth()
648 protected void recalcWidthCache()
650 if (totalColumnWidth == -1)
652 totalColumnWidth = 0;
653 for (int i = 0; i < tableColumns.size(); ++i)
655 totalColumnWidth += ((TableColumn) tableColumns.get(i)).getWidth();
661 * Sets the {@link #totalColumnWidth} field to <code>-1</code>.
663 * @see #recalcWidthCache()
665 private void invalidateWidthCache()
667 totalColumnWidth = -1;