/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package gov.nasa.arc.mct.table.model;
import gov.nasa.arc.mct.table.utils.ListenerManager;
import gov.nasa.arc.mct.table.utils.ListenerNotifier;
import javax.swing.AbstractListModel;
import javax.swing.ListModel;
import javax.swing.table.AbstractTableModel;
/**
* Implements a table model where each cell has a unique identifier that
* is used along with a labeling algorithm to determine row and column
* labels.
*/
@SuppressWarnings("serial")
public abstract class LabeledTableModel extends AbstractTableModel {
private TableLabelingAlgorithm algorithm;
private TableOrientation orientation;
/** Table row labels. */
protected String[] rowLabels;
/** Table column labels. */
protected String[] columnLabels;
/** Table cell labels. */
protected String[][] cellLabels;
/** A model for the row labels. */
LabelListModel rowLabelModel;
/** A model for the column labels. */
LabelListModel columnLabelModel;
/** A manager for listeners. */
private ListenerManager listenerManager = new ListenerManager();
/**
* Creates an instance of the table based on the given labeling
* algorithm. The labels are not updated immediately upon construction,
* since derived classes may not be fully constructed. Instead, the
* user should call xyz to update the labels before using the table
* model. Subsequent changes to the table model will cause automatic
* updating of the labels.
*
* @param algorithm the table labeling algorithm
* @param orientation the table orientation
*/
public LabeledTableModel(TableLabelingAlgorithm algorithm, TableOrientation orientation) {
this.algorithm = algorithm;
this.orientation = orientation;
algorithm.setOrientation(orientation);
rowLabelModel = new LabelListModel() {
private static final long serialVersionUID = 1L;
@Override
public Object getElementAt(int index) {
return getRowName(index);
}
@Override
public int getSize() {
return getRowCount();
}
};
columnLabelModel = new LabelListModel() {
private static final long serialVersionUID = 1L;
@Override
public Object getElementAt(int index) {
return getColumnName(index);
}
@Override
public int getSize() {
return getColumnCount();
}
};
}
/**
* Adds a listener for events fired when the labels have been updated.
*
* @param listener the label change listener.
*/
public void addLabelChangeListener(LabelChangeListener listener) {
listenerManager.addListener(LabelChangeListener.class, listener);
}
/**
* Removes the listener for events.
* @param listener the label change listener.
*/
public void removeLabelChangeListener(LabelChangeListener listener) {
listenerManager.removeListener(LabelChangeListener.class, listener);
}
/**
* Gets the table type.
* @return 2-D TableType
*/
public TableType getTableType() {
return TableType.TWO_DIMENSIONAL;
}
/**
* Get the number of objects represented by the table.
*
* @return the number of objects.
*/
protected abstract int getObjectCount();
/**
* Gets the number of attributes for each object in the table.
* Not all objects may have values for each of the attributes.
*
* @return the number of attributes.
*/
protected abstract int getAttributeCount();
/**
* Gets the object at the given row and column. The representation on the
* screen may be at the transposed position, depending on the table orientation.
*
* @param rowIndex the row at which to get the object.
* @param columnIndex the column at which to get the object.
* @return the object at that row and column.
*/
protected abstract Object getObjectAt(int rowIndex, int columnIndex);
/**
* Gets the identifier for the object at a given position. The identifier
* is used by the labeling algorithm to compute row, column, and cell
* labels.
*
* @param rowIndex the row at which to get the object identifier
* @param columnIndex the column at which to get the object identifier
* @return the object identifier at that row and column
*/
protected abstract String getObjectIdentifierAt(int rowIndex, int columnIndex);
@Override
public final int getRowCount() {
if (orientation == TableOrientation.ROW_MAJOR) {
return getObjectCount();
} else {
return getAttributeCount();
}
}
@Override
public final int getColumnCount() {
if (orientation == TableOrientation.ROW_MAJOR) {
return getAttributeCount();
} else {
return getObjectCount();
}
}
@Override
public final Object getValueAt(int rowIndex, int columnIndex) {
if (orientation == TableOrientation.ROW_MAJOR) {
return getObjectAt(rowIndex, columnIndex);
} else {
return getObjectAt(columnIndex, rowIndex);
}
}
/**
* Gets the stored value at specific row and column indices.
* @param rowIndex the row index.
* @param columnIndex the column index.
* @return the stored object value.
*/
public final Object getStoredValueAt(int rowIndex, int columnIndex) {
if (orientation == TableOrientation.ROW_MAJOR) {
return getStoredObjectAt(rowIndex, columnIndex);
} else {
return getStoredObjectAt(columnIndex, rowIndex);
}
}
/**
* Gets the stored object at specific object and attribute indices.
* @param objectIndex the object index.
* @param attributeIndex the attribute index.
* @return the stored object.
*/
protected abstract Object getStoredObjectAt(int objectIndex, int attributeIndex);
/**
* Gets the current table orientation.
*
* @return the table orientation.
*/
public TableOrientation getOrientation() {
return orientation;
}
/**
* Sets the table orientation. When the orientation is changed,
* the labels are recalculated, and a structure change event
* is fired so the entire table redraws. If the new orientation
* is the same as the old orientation, does nothing.
*
* @param newOrientation the new table orientation.
*/
public void setOrientation(TableOrientation newOrientation) {
algorithm.setOrientation(newOrientation);
if (newOrientation != orientation) {
orientation = newOrientation;
updateLabels();
fireTableStructureChanged();
}
}
/**
* Handles an event where the row, column, or cell labels have changed.
* Triggers a structure changed event so that the table will be redrawn
* completely.
*/
public void fireLabelsChanged() {
rowLabelModel.fireLabelsChanged();
columnLabelModel.fireLabelsChanged();
listenerManager.fireEvent(LabelChangeListener.class, new ListenerNotifier<LabelChangeListener>() {
@Override
public void notifyEvent(LabelChangeListener listener) {
listener.labelsChanged();
}
});
}
@Override
public void fireTableCellUpdated(int row, int column) {
if (orientation == TableOrientation.ROW_MAJOR) {
super.fireTableCellUpdated(row, column);
} else {
super.fireTableCellUpdated(column, row);
}
}
/**
* Recalculates all row, column, and cell labels. This method is executed
* automatically when the table model changes. However, the user can
* call this method at other times, if desired.
*/
public void updateLabels() {
rowLabels = new String[getRowCount()];
columnLabels = new String[getColumnCount()];
cellLabels = new String[getRowCount()][getColumnCount()];
algorithm.computeLabels(this);
rowLabelModel.fireLabelsChanged();
columnLabelModel.fireLabelsChanged();
}
/**
* Tests whether there are row labels. If any row label is nonempty,
* then we have row labels.
*
* @return true, if at least one row label is nonempty
*/
public boolean hasRowLabels() {
if (rowLabels == null) {
return true; // Don't yet have labels.
}
for (String label : rowLabels) {
if (!label.isEmpty()) {
return true;
}
}
return false;
}
/**
* Tests whether there are column labels. If any column label is nonempty,
* then we have column labels.
*
* @return true, if at least one column label is nonempty
*/
public boolean hasColumnLabels() {
if (columnLabels == null) {
return true; // Don't yet have defined labels.
}
for (String label : columnLabels) {
if (!label.isEmpty()) {
return true;
}
}
return false;
}
/**
* Gets a row label, possibly abbreviated.
*
* @param rowIndex the index of the row for which to get the label
* @return the row label
*/
public String getRowName(int rowIndex) {
return getFullRowName(rowIndex);
}
/**
* Gets the unabbrevaited label for a row.
*
* @param rowIndex the index of the row for which to get the label
* @return the row label
*/
public String getFullRowName(int rowIndex) {
if (rowLabels==null || rowIndex >= rowLabels.length) {
return ""; // Must not have updated labels yet.
} else {
return rowLabels[rowIndex];
}
}
/**
* Sets a row label. This is designed to be called by the labeling
* algorithm.
*
* @param rowIndex the index of the row label to set
* @param label the new row label
*/
void setRowName(int rowIndex, String label) {
rowLabels[rowIndex] = label;
}
/**
* Gets a column label, possibly abbreviated.
*
* @param columnIndex the index of the column for which to get the label
* @return the column label
*/
@Override
public String getColumnName(int columnIndex) {
return getFullColumnName(columnIndex);
}
/**
* Gets the unabbreviated label for a column.
*
* @param columnIndex the index of the column for which to get the label
* @return the column label
*/
public String getFullColumnName(int columnIndex) {
if (columnLabels==null || columnIndex >= columnLabels.length) {
return ""; // Must not have updated labels yet.
} else {
return columnLabels[columnIndex];
}
}
/**
* Sets a column label. This is designed to be called by the labeling
* algorithm.
*
* @param columnIndex the index of the column label to set
* @param label the new column label
*/
void setColumnName(int columnIndex, String label) {
columnLabels[columnIndex] = label;
}
/**
* Gets a cell label, possibly abbreviated.
*
* @param rowIndex the index of the row containing the cell
* @param columnIndex the index of the column containing the cell
* @return the cell label
*/
public String getCellName(int rowIndex, int columnIndex) {
return getFullCellName(rowIndex, columnIndex);
}
/**
* Gets the unabbreviated label for a cell.
*
* @param rowIndex the index of the row containing the cell
* @param columnIndex the index of the column containing the cell
* @return the cell label
*/
public final String getFullCellName(int rowIndex, int columnIndex) {
if (rowIndex >= cellLabels.length || columnIndex >= cellLabels[rowIndex].length) {
return "";
} else {
return cellLabels[rowIndex][columnIndex];
}
}
/**
* Gets a label for an object in a cell. This method takes into consideration
* the orientation of the table.
*
* @param objectIndex the index of the object within the table
* @param attributeIndex the index of the attribute object
* @return the label for the object
*/
String getObjectName(int objectIndex, int attributeIndex) {
if (orientation == TableOrientation.ROW_MAJOR) {
return getCellName(objectIndex, attributeIndex);
} else {
return getCellName(attributeIndex, objectIndex);
}
}
/**
* Sets a cell label. This is designed to be called by the labeling
* algorithm.
*
* @param rowIndex the index of the row containing the cell
* @param columnIndex the index of the column containing the cell
* @param label the new cell label
*/
void setCellName(int rowIndex, int columnIndex, String label) {
cellLabels[rowIndex][columnIndex] = label;
}
/**
* Gets a list model for the row labels.
*
* @return the list model
*/
public ListModel getRowLabelModel() {
return rowLabelModel;
}
/**
* Gets a list model for the column labels.
*
* @return the list model
*/
public ListModel getColumnLabelModel() {
return columnLabelModel;
}
/**
* Gets the unique identifier for the data in the indicated cell.
* The unique identifier is often used with a table labeling
* algorithm to determine abbreviated row, column, and cell
* labels.
*
* @param rowIndex the row containing the cell
* @param columnIndex the column containing the cell
* @return the unique identifier for the cell
*/
public final String getIdentifierAt(int rowIndex, int columnIndex) {
if (orientation == TableOrientation.ROW_MAJOR) {
return getObjectIdentifierAt(rowIndex, columnIndex);
} else {
return getObjectIdentifierAt(columnIndex, rowIndex);
}
}
/**
* Tests whether a value can be placed into a position within a table. The
* new position may cause a row or column to be inserted.
*
* @param rowIndex the row at which to place the new item
* @param columnIndex the column at which to place the new item
* @param isInsertRow true, if a new row should be inserted above the position
* @param isInsertColumn true, if a new column should be inserted to the left of the position
* @return true, if a value can be placed or inserted at the position
*/
public final boolean canSetValueAt(int rowIndex, int columnIndex, boolean isInsertRow, boolean isInsertColumn) {
if (orientation == TableOrientation.ROW_MAJOR) {
return canSetObjectAt(rowIndex, columnIndex, isInsertRow, isInsertColumn);
} else {
return canSetObjectAt(columnIndex, rowIndex, isInsertColumn, isInsertRow);
}
}
/**
* Checks whether object can be set at specific row and column indices along with isInsertRow and isInsertColumn boolean flags.
* @param rowIndex the row index.
* @param columnIndex the column index.
* @param isInsertRow boolean flag to check for whether can insert row.
* @param isInsertColumn boolean flag to check for whether can insert column.
* @return boolean flag to check whether object can be set.
*/
protected abstract boolean canSetObjectAt(int rowIndex, int columnIndex, boolean isInsertRow, boolean isInsertColumn);
/**
* Implements a list model with an additional method that makes it
* easy to fire an event when the list contents have changed.
*/
private static abstract class LabelListModel extends AbstractListModel {
private static final long serialVersionUID = 1L;
/**
* Notify all listeners that the list contents have changed.
*/
protected void fireLabelsChanged() {
fireContentsChanged(this, 0, getSize());
}
}
/**
* Sets the object value at specific row and column indices along with isInsertRow and isInsertColumn boolean flags.
* @param aValue the object value.
* @param rowIndex the row index.
* @param columnIndex the column index.
* @param isInsertRow boolean flag to check for whether can insert row.
* @param isInsertColumn boolean flag to check for whether can insert column.
*/
public final void setValueAt(Object aValue, int rowIndex, int columnIndex, boolean isInsertRow, boolean isInsertColumn) {
if (orientation == TableOrientation.ROW_MAJOR) {
setObjectAt(aValue, rowIndex, columnIndex, isInsertRow, isInsertColumn);
} else {
setObjectAt(aValue, columnIndex, rowIndex, isInsertColumn, isInsertRow);
}
updateLabels();
}
/**
* Sets the object at the given row and column. The representation on the
* screen may be at the transposed position, depending on the table orientation.
*
* @param value the new object at that row and column.
* @param rowIndex the row at which to set the object.
* @param columnIndex the column at which to set the object.
* @param isInsertRow boolean flag to check for whether can insert row.
* @param isInsertColumn boolean flag to check for whether can insert column.
*/
protected abstract void setObjectAt(Object value, int rowIndex, int columnIndex, boolean isInsertRow, boolean isInsertColumn);
/**
* Tests whether the table is a skeleton, ready for values to be
* dropped in to flesh out the table cells and columns. A skeleton
* table needs to have rows and column labels displayed even though
* the labels will be empty.
*
* @return true, if the table is a skeleton
*/
public abstract boolean isSkeleton();
}