001package jmri.util.swing;
002
003import java.util.Collections;
004import java.util.Enumeration;
005import java.util.Vector;
006import javax.swing.table.DefaultTableColumnModel;
007import javax.swing.table.TableColumn;
008
009/**
010 * Taken from http://www.stephenkelvin.de/XTableColumnModel/
011 * {@code XTableColumnModel} extends the DefaultTableColumnModel . It provides a
012 * comfortable way to hide/show columns. Columns keep their positions when
013 * hidden and shown again.
014 * <p>
015 * In order to work with JTable it cannot add any events to
016 * {@code TableColumnModelListener}. Therefore hiding a column will result in
017 * {@code columnRemoved} event and showing it again will notify listeners of a
018 * {@code columnAdded}, and possibly a {@code columnMoved} event. For the same
019 * reason the following methods still deal with visible columns only:
020 * getColumnCount(), getColumns(), getColumnIndex(), getColumn() There are
021 * overloaded versions of these methods that take a parameter
022 * {@code onlyVisible} which let's you specify whether you want invisible
023 * columns taken into account.
024 *
025 * @version 0.9 04/03/01
026 * @author Stephen Kelvin, mail@StephenKelvin.de
027 * @see DefaultTableColumnModel
028 */
029public class XTableColumnModel extends DefaultTableColumnModel {
030
031    /**
032     * Array of TableColumn objects in this model. Holds all column objects,
033     * regardless of their visibility
034     */
035    protected Vector<TableColumn> allTableColumns = new Vector<>();
036
037    /**
038     * Creates an extended table column model.
039     */
040    public XTableColumnModel() {
041    }
042
043    /**
044     * Sets the visibility of the specified TableColumn. The call is ignored if
045     * the TableColumn is not found in this column model or its visibility
046     * status did not change.
047     * <p>
048     *
049     * @param column  the column to show/hide
050     * @param visible its new visibility status
051     */
052    // listeners will receive columnAdded()/columnRemoved() event
053    public void setColumnVisible(TableColumn column, boolean visible) {
054        if (!visible) {
055            super.removeColumn(column);
056        } else {
057            // find the visible index of the column:
058            // iterate through both collections of visible and all columns, counting
059            // visible columns up to the one that's about to be shown again
060            int noVisibleColumns = tableColumns.size();
061            int noInvisibleColumns = allTableColumns.size();
062            int visibleIndex = 0;
063
064            for (int invisibleIndex = 0; invisibleIndex < noInvisibleColumns; ++invisibleIndex) {
065                TableColumn visibleColumn = (visibleIndex < noVisibleColumns ? tableColumns.get(visibleIndex) : null);
066                TableColumn testColumn = allTableColumns.get(invisibleIndex);
067
068                if (testColumn == column) {
069                    if (visibleColumn != column) {
070                        super.addColumn(column);
071                        super.moveColumn(tableColumns.size() - 1, visibleIndex);
072                    }
073                    return;
074                }
075                if (testColumn == visibleColumn) {
076                    ++visibleIndex;
077                }
078            }
079        }
080    }
081
082    /**
083     * Makes all columns in this model visible
084     */
085    public void setAllColumnsVisible() {
086        int noColumns = allTableColumns.size();
087
088        for (int columnIndex = 0; columnIndex < noColumns; ++columnIndex) {
089            TableColumn visibleColumn = (columnIndex < tableColumns.size() ? tableColumns.get(columnIndex) : null);
090            TableColumn invisibleColumn = allTableColumns.get(columnIndex);
091
092            if (visibleColumn != invisibleColumn) {
093                super.addColumn(invisibleColumn);
094                super.moveColumn(tableColumns.size() - 1, columnIndex);
095            }
096        }
097    }
098
099    /**
100     * Maps the index of the column in the table model at
101     * {@code modelColumnIndex} to the TableColumn object. There may be multiple
102     * TableColumn objects showing the same model column, though this is
103     * uncommon.
104     *
105     * @param modelColumnIndex index of column in table model
106     * @return the first column, visible or invisible, with the specified index
107     *         or null if no such column
108     */
109    public TableColumn getColumnByModelIndex(int modelColumnIndex) {
110        for (int columnIndex = 0; columnIndex < allTableColumns.size(); ++columnIndex) {
111            TableColumn column = allTableColumns.get(columnIndex);
112            if (column.getModelIndex() == modelColumnIndex) {
113                return column;
114            }
115        }
116        return null;
117    }
118
119    /**
120     * Checks whether the specified column is currently visible.
121     *
122     * @param aColumn column to check
123     * @return visibility of specified column (false if there is no such column
124     *         at all. [It's not visible, right?])
125     */
126    public boolean isColumnVisible(TableColumn aColumn) {
127        return (tableColumns.indexOf(aColumn) >= 0);
128    }
129
130    /**
131     * Append {@code column} to the right of existing columns. Posts
132     * {@code columnAdded} event.
133     *
134     * @param column The column to be added
135     * @see #removeColumn
136     * @exception IllegalArgumentException if {@code column} is {@code null}
137     */
138    @Override
139    public void addColumn(TableColumn column) {
140        allTableColumns.add(column);
141        super.addColumn(column);
142    }
143
144    /**
145     * Removes {@code column} from this column model. Posts
146     * {@code columnRemoved} event. Will do nothing if the column is not in this
147     * model.
148     *
149     * @param column the column to be added
150     * @see #addColumn
151     */
152    @Override
153    public void removeColumn(TableColumn column) {
154        int allColumnsIndex = allTableColumns.indexOf(column);
155        if (allColumnsIndex != -1) {
156            allTableColumns.remove(allColumnsIndex);
157        }
158        super.removeColumn(column);
159    }
160
161    /**
162     * Moves the column from {@code columnIndex} to {@code newIndex}. Posts
163     * {@code columnMoved} event. Will not move any columns if
164     * {@code columnIndex} equals {@code newIndex}. This method also posts a
165     * {@code columnMoved} event to its listeners.
166     *
167     * @param columnIndex index of column to be moved
168     * @param newIndex    new index of the column
169     * @exception IllegalArgumentException if either {@code oldIndex} or
170     *                                     {@code newIndex} are not in [0,
171     *                                     getColumnCount() - 1]
172     */
173    @Override
174    public void moveColumn(int columnIndex, int newIndex) {
175        moveColumn(columnIndex, newIndex, true);
176    }
177
178    /**
179     * Moves the column from {@code columnIndex} to {@code newIndex}. Posts
180     * {@code columnMoved} event. Will not move any columns if
181     * {@code columnIndex} equals {@code newIndex}. This method also posts a
182     * {@code columnMoved} event to its listeners if a visible column moves.
183     *
184     * @param columnIndex index of column to be moved
185     * @param newIndex    new index of the column
186     * @param onlyVisible true if this should only move a visible column; false
187     *                    to move any column
188     * @exception IllegalArgumentException if either {@code oldIndex} or
189     *                                     {@code newIndex} are not in [0,
190     *                                     getColumnCount(onlyVisible) - 1]
191     */
192    public void moveColumn(int columnIndex, int newIndex, boolean onlyVisible) {
193        if ((columnIndex < 0) || (columnIndex >= getColumnCount(onlyVisible))
194                || (newIndex < 0) || (newIndex >= getColumnCount(onlyVisible))) {
195            throw new IllegalArgumentException("moveColumn() - Index out of range");
196        }
197
198        if (onlyVisible) {
199            if (columnIndex != newIndex) {
200                // columnIndex and newIndex are indexes of visible columns, so need
201                // to get index of column in list of all columns
202                int allColumnsColumnIndex = allTableColumns.indexOf(tableColumns.get(columnIndex));
203                int allColumnsNewIndex = allTableColumns.indexOf(tableColumns.get(newIndex));
204
205                TableColumn column = allTableColumns.remove(allColumnsColumnIndex);
206                allTableColumns.add(allColumnsNewIndex, column);
207            }
208
209            super.moveColumn(columnIndex, newIndex);
210        } else {
211            if (columnIndex != newIndex) {
212                // columnIndex and newIndex are indexes of all columns, so need
213                // to get index of column in list of visible columns
214                int visibleColumnIndex = tableColumns.indexOf(allTableColumns.get(columnIndex));
215                int visibleNewIndex = tableColumns.indexOf(allTableColumns.get(newIndex));
216
217                TableColumn column = allTableColumns.remove(columnIndex);
218                allTableColumns.add(newIndex, column);
219                // call super moveColumn if both indexes are visible
220                if (visibleColumnIndex != -1 && visibleNewIndex != -1) {
221                    super.moveColumn(visibleColumnIndex, visibleNewIndex);
222                }
223            }
224
225        }
226    }
227
228    /**
229     * Returns the total number of columns in this model.
230     *
231     * @param onlyVisible if set only visible columns will be counted
232     * @return the number of columns in the {@code tableColumns} array
233     * @see #getColumns
234     */
235    public int getColumnCount(boolean onlyVisible) {
236        return getColumnList(onlyVisible).size();
237    }
238
239    /**
240     * Returns an {@code Enumeration} of all the columns in the model.
241     *
242     * @param onlyVisible if set all invisible columns will be missing from the
243     *                    enumeration.
244     * @return an {@code Enumeration} of the columns in the model
245     */
246    public Enumeration<TableColumn> getColumns(boolean onlyVisible) {
247        return Collections.enumeration(getColumnList(onlyVisible));
248    }
249
250    /**
251     * Returns the position of the first column whose identifier equals
252     * {@code identifier}. Position is the index in all visible columns if
253     * {@code onlyVisible} is true or else the index in all columns.
254     *
255     * @param identifier  the identifier object to search for
256     * @param onlyVisible if set searches only visible columns
257     *
258     * @return the index of the first column whose identifier equals
259     *         {@code identifier}
260     *
261     * @exception IllegalArgumentException if {@code identifier} is
262     *                                     {@code null}, or if no
263     *                                     {@code TableColumn} has this
264     *                                     {@code identifier}
265     * @see #getColumn
266     */
267    public int getColumnIndex(Object identifier, boolean onlyVisible) {
268        if (identifier == null) {
269            throw new IllegalArgumentException("Identifier is null");
270        }
271
272        Vector<TableColumn> columns = getColumnList(onlyVisible);
273        int noColumns = columns.size();
274        TableColumn column;
275
276        for (int columnIndex = 0; columnIndex < noColumns; ++columnIndex) {
277            column = columns.get(columnIndex);
278
279            if (identifier.equals(column.getIdentifier())) {
280                return columnIndex;
281            }
282        }
283
284        throw new IllegalArgumentException("Identifier not found");
285    }
286
287    /**
288     * Returns the {@code TableColumn} object for the column at
289     * {@code columnIndex}.
290     *
291     * @param columnIndex the index of the column desired
292     * @param onlyVisible if set columnIndex is meant to be relative to all
293     *                    visible columns only else it is the index in all
294     *                    columns
295     *
296     * @return the {@code TableColumn} object for the column at
297     *         {@code columnIndex}
298     */
299    public TableColumn getColumn(int columnIndex, boolean onlyVisible) {
300        return getColumnList(onlyVisible).get(columnIndex);
301    }
302
303    /**
304     * Get the list of columns. This list may be only the visible columns or may
305     * be the list of all columns.
306     *
307     * @param onlyVisible true if the list should only contain visible columns;
308     *                    false otherwise
309     * @return the list of columns
310     */
311    private Vector<TableColumn> getColumnList(boolean onlyVisible) {
312        return (onlyVisible ? tableColumns : allTableColumns);
313    }
314}