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     *
048     * @param column  the column to show/hide
049     * @param visible its new visibility status
050     */
051    // listeners will receive columnAdded()/columnRemoved() event
052    public void setColumnVisible(TableColumn column, boolean visible) {
053        if (!visible) {
054            super.removeColumn(column);
055        } else {
056            // find the visible index of the column:
057            // iterate through both collections of visible and all columns, counting
058            // visible columns up to the one that's about to be shown again
059            int noVisibleColumns = tableColumns.size();
060            int noInvisibleColumns = allTableColumns.size();
061            int visibleIndex = 0;
062
063            for (int invisibleIndex = 0; invisibleIndex < noInvisibleColumns; ++invisibleIndex) {
064                TableColumn visibleColumn = (visibleIndex < noVisibleColumns ? tableColumns.get(visibleIndex) : null);
065                TableColumn testColumn = allTableColumns.get(invisibleIndex);
066
067                if (testColumn == column) {
068                    if (visibleColumn != column) {
069                        super.addColumn(column);
070                        super.moveColumn(tableColumns.size() - 1, visibleIndex);
071                    }
072                    return;
073                }
074                if (testColumn == visibleColumn) {
075                    ++visibleIndex;
076                }
077            }
078        }
079    }
080
081    /**
082     * Makes all columns in this model visible
083     */
084    public void setAllColumnsVisible() {
085        int noColumns = allTableColumns.size();
086
087        for (int columnIndex = 0; columnIndex < noColumns; ++columnIndex) {
088            TableColumn visibleColumn = (columnIndex < tableColumns.size() ? tableColumns.get(columnIndex) : null);
089            TableColumn invisibleColumn = allTableColumns.get(columnIndex);
090
091            if (visibleColumn != invisibleColumn) {
092                super.addColumn(invisibleColumn);
093                super.moveColumn(tableColumns.size() - 1, columnIndex);
094            }
095        }
096    }
097
098    /**
099     * Maps the index of the column in the table model at
100     * {@code modelColumnIndex} to the TableColumn object. There may be multiple
101     * TableColumn objects showing the same model column, though this is
102     * uncommon.
103     *
104     * @param modelColumnIndex index of column in table model
105     * @return the first column, visible or invisible, with the specified index
106     *         or null if no such column
107     */
108    public TableColumn getColumnByModelIndex(int modelColumnIndex) {
109        for (int columnIndex = 0; columnIndex < allTableColumns.size(); ++columnIndex) {
110            TableColumn column = allTableColumns.get(columnIndex);
111            if (column.getModelIndex() == modelColumnIndex) {
112                return column;
113            }
114        }
115        return null;
116    }
117
118    /**
119     * Checks whether the specified column is currently visible.
120     *
121     * @param aColumn column to check
122     * @return visibility of specified column (false if there is no such column
123     *         at all. [It's not visible, right?])
124     */
125    public boolean isColumnVisible(TableColumn aColumn) {
126        return (tableColumns.indexOf(aColumn) >= 0);
127    }
128
129    /**
130     * Append {@code column} to the right of existing columns. Posts
131     * {@code columnAdded} event.
132     *
133     * @param column The column to be added
134     * @see #removeColumn
135     * @exception IllegalArgumentException if {@code column} is {@code null}
136     */
137    @Override
138    public void addColumn(TableColumn column) {
139        allTableColumns.add(column);
140        super.addColumn(column);
141    }
142
143    /**
144     * Removes {@code column} from this column model. Posts
145     * {@code columnRemoved} event. Will do nothing if the column is not in this
146     * model.
147     *
148     * @param column the column to be added
149     * @see #addColumn
150     */
151    @Override
152    public void removeColumn(TableColumn column) {
153        int allColumnsIndex = allTableColumns.indexOf(column);
154        if (allColumnsIndex != -1) {
155            allTableColumns.remove(allColumnsIndex);
156        }
157        super.removeColumn(column);
158    }
159
160    /**
161     * Moves the column from {@code columnIndex} to {@code newIndex}. Posts
162     * {@code columnMoved} event. Will not move any columns if
163     * {@code columnIndex} equals {@code newIndex}. This method also posts a
164     * {@code columnMoved} event to its listeners.
165     *
166     * @param columnIndex index of column to be moved
167     * @param newIndex    new index of the column
168     * @exception IllegalArgumentException if either {@code oldIndex} or
169     *                                     {@code newIndex} are not in [0,
170     *                                     getColumnCount() - 1]
171     */
172    @Override
173    public void moveColumn(int columnIndex, int newIndex) {
174        moveColumn(columnIndex, newIndex, true);
175    }
176
177    /**
178     * Moves the column from {@code columnIndex} to {@code newIndex}. Posts
179     * {@code columnMoved} event. Will not move any columns if
180     * {@code columnIndex} equals {@code newIndex}. This method also posts a
181     * {@code columnMoved} event to its listeners if a visible column moves.
182     *
183     * @param columnIndex index of column to be moved
184     * @param newIndex    new index of the column
185     * @param onlyVisible true if this should only move a visible column; false
186     *                    to move any column
187     * @exception IllegalArgumentException if either {@code oldIndex} or
188     *                                     {@code newIndex} are not in [0,
189     *                                     getColumnCount(onlyVisible) - 1]
190     */
191    public void moveColumn(int columnIndex, int newIndex, boolean onlyVisible) {
192        if ((columnIndex < 0) || (columnIndex >= getColumnCount(onlyVisible))
193                || (newIndex < 0) || (newIndex >= getColumnCount(onlyVisible))) {
194            throw new IllegalArgumentException("moveColumn() - Index out of range");
195        }
196
197        if (onlyVisible) {
198            if (columnIndex != newIndex) {
199                // columnIndex and newIndex are indexes of visible columns, so need
200                // to get index of column in list of all columns
201                int allColumnsColumnIndex = allTableColumns.indexOf(tableColumns.get(columnIndex));
202                int allColumnsNewIndex = allTableColumns.indexOf(tableColumns.get(newIndex));
203
204                TableColumn column = allTableColumns.remove(allColumnsColumnIndex);
205                allTableColumns.add(allColumnsNewIndex, column);
206            }
207
208            super.moveColumn(columnIndex, newIndex);
209        } else {
210            if (columnIndex != newIndex) {
211                // columnIndex and newIndex are indexes of all columns, so need
212                // to get index of column in list of visible columns
213                int visibleColumnIndex = tableColumns.indexOf(allTableColumns.get(columnIndex));
214                int visibleNewIndex = tableColumns.indexOf(allTableColumns.get(newIndex));
215
216                TableColumn column = allTableColumns.remove(columnIndex);
217                allTableColumns.add(newIndex, column);
218                // call super moveColumn if both indexes are visible
219                if (visibleColumnIndex != -1 && visibleNewIndex != -1) {
220                    super.moveColumn(visibleColumnIndex, visibleNewIndex);
221                }
222            }
223
224        }
225    }
226
227    /**
228     * Returns the total number of columns in this model.
229     *
230     * @param onlyVisible if set only visible columns will be counted
231     * @return the number of columns in the {@code tableColumns} array
232     * @see #getColumns
233     */
234    public int getColumnCount(boolean onlyVisible) {
235        return getColumnList(onlyVisible).size();
236    }
237
238    /**
239     * Returns an {@code Enumeration} of all the columns in the model.
240     *
241     * @param onlyVisible if set all invisible columns will be missing from the
242     *                    enumeration.
243     * @return an {@code Enumeration} of the columns in the model
244     */
245    public Enumeration<TableColumn> getColumns(boolean onlyVisible) {
246        return Collections.enumeration(getColumnList(onlyVisible));
247    }
248
249    /**
250     * Returns the position of the first column whose identifier equals
251     * {@code identifier}. Position is the index in all visible columns if
252     * {@code onlyVisible} is true or else the index in all columns.
253     *
254     * @param identifier  the identifier object to search for
255     * @param onlyVisible if set searches only visible columns
256     *
257     * @return the index of the first column whose identifier equals
258     *         {@code identifier}
259     *
260     * @exception IllegalArgumentException if {@code identifier} is
261     *                                     {@code null}, or if no
262     *                                     {@code TableColumn} has this
263     *                                     {@code identifier}
264     * @see #getColumn
265     */
266    public int getColumnIndex(Object identifier, boolean onlyVisible) {
267        if (identifier == null) {
268            throw new IllegalArgumentException("Identifier is null");
269        }
270
271        Vector<TableColumn> columns = getColumnList(onlyVisible);
272        int noColumns = columns.size();
273        TableColumn column;
274
275        for (int columnIndex = 0; columnIndex < noColumns; ++columnIndex) {
276            column = columns.get(columnIndex);
277
278            if (identifier.equals(column.getIdentifier())) {
279                return columnIndex;
280            }
281        }
282
283        throw new IllegalArgumentException("Identifier not found");
284    }
285
286    /**
287     * Returns the {@code TableColumn} object for the column at
288     * {@code columnIndex}.
289     *
290     * @param columnIndex the index of the column desired
291     * @param onlyVisible if set columnIndex is meant to be relative to all
292     *                    visible columns only else it is the index in all
293     *                    columns
294     *
295     * @return the {@code TableColumn} object for the column at
296     *         {@code columnIndex}
297     */
298    public TableColumn getColumn(int columnIndex, boolean onlyVisible) {
299        return getColumnList(onlyVisible).get(columnIndex);
300    }
301
302    /**
303     * Get the list of columns. This list may be only the visible columns or may
304     * be the list of all columns.
305     *
306     * @param onlyVisible true if the list should only contain visible columns;
307     *                    false otherwise
308     * @return the list of columns
309     */
310    private Vector<TableColumn> getColumnList(boolean onlyVisible) {
311        return (onlyVisible ? tableColumns : allTableColumns);
312    }
313}