/******************************************************************************* * 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.components.AbstractComponent; import gov.nasa.arc.mct.components.FeedProvider; import gov.nasa.arc.mct.components.Placeholder; import gov.nasa.arc.mct.evaluator.api.Evaluator; import gov.nasa.arc.mct.table.utils.NoSizeList; import gov.nasa.arc.mct.table.view.DisplayedValue; import gov.nasa.arc.mct.table.view.LabelAbbreviations; import gov.nasa.arc.mct.table.view.TableCellSettings; import gov.nasa.arc.mct.table.view.TableViewManifestation; import java.awt.Point; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Implements a labeled table model that represents a nested object * structure of MCT components. */ public class ComponentTableModel extends LabeledTableModel { private static final long serialVersionUID = 1L; /** The 2-dimensional matrix of components making up this table model. */ private TableStructure structure; private Map<String,List<Point>> componentLocations; private Map<String, Object> values = new HashMap<String, Object>(); private Map<String, TableCellSettings> cellSettings = new HashMap<String, TableCellSettings>(); private Map<String, LabelAbbreviations> cellLabelAbbreviations = new HashMap<String, LabelAbbreviations>(); private NoSizeList<LabelAbbreviations> rowLabelAbbreviations = new NoSizeList<LabelAbbreviations>(); private NoSizeList<LabelAbbreviations> columnLabelAbbreviations = new NoSizeList<LabelAbbreviations>(); /** The maximum number of decimals for each column, if known. */ private NoSizeList<Integer> maxDecimalsForColumn = new NoSizeList<Integer>(); private TableViewManifestation tableViewManifestation; /** * Creates a new table model from a table structure object and a * labeling algorithm. * * @param structure the object representing the underlying MCT component structure. * @param algorithm the labeling algorithm. * @param tableViewManifestation the table view manifestation. */ public ComponentTableModel( TableStructure structure, TableLabelingAlgorithm algorithm, TableViewManifestation tableViewManifestation ) { super(algorithm, TableOrientation.ROW_MAJOR); this.structure = structure; this.tableViewManifestation = tableViewManifestation; updateLocations(); } /** * Updates the table structure in response to knowledge that the structure * of the root component in the table has changed. Users of the table model * should call this when they receive notification that children have been * added to or removed from the component that is backing up this model. */ public void notifyTableStructureChanged() { structure.notifyTableStructureChanged(); } /** * Returns a unique key for a given component. This key is used by the * code that responds to a feed update to pass along changes to the * component's value. * * @param component the component for which to determine the key * @return a unique key for the component */ public String getKey(AbstractComponent component) { AbstractComponent delegate = component; Evaluator e = component.getCapability(Evaluator.class); if (e != null && component.getComponents().size() > 1) { return component.getComponentId(); } FeedProvider fp = component.getCapability(FeedProvider.class); if (fp != null) { return fp.getSubscriptionId(); } return delegate.getComponentId(); } private void updateLocations() { componentLocations = new HashMap<String,List<Point>>(); for (int row=0; row < structure.getRowCount(); ++row) { for (int col=0; col < structure.getColumnCount(); ++col) { AbstractComponent component = structure.getValue(row, col); if (component != null) { component.addViewManifestation(tableViewManifestation); List<Point> locations = componentLocations.get(getKey(component)); if (locations == null) { locations = new ArrayList<Point>(); componentLocations.put(getKey(component), locations); } locations.add(new Point(col,row)); } } } } @Override protected int getObjectCount() { return structure.getRowCount(); } @Override protected int getAttributeCount() { return structure.getColumnCount(); } @Override protected String getObjectIdentifierAt(int rowIndex, int columnIndex) { AbstractComponent component = structure.getValue(rowIndex, columnIndex); if (component == null) { return ""; } else if (component == AbstractComponent.NULL_COMPONENT) { return "\u00A0"; } else { return getCanonicalName(component); } } /** * Gets a complete identifier labeling the component. These <em>canonical * names</em> for each table cell are used in the table labeling algorithm * to calculate labels for columns, rows, and cells. The canonical name * is the canonical name of the feed provider, if it exists and is not * empty. Otherwise it is the display name for the component. * * @param component the component for which we need the canonical name * @return the canonical name for the component */ private String getCanonicalName(AbstractComponent component) { String canonicalName = null; // Try to get the canonical name for the feed provider. FeedProvider feedProvider = component.getCapability(FeedProvider.class); if (feedProvider != null) { canonicalName = feedProvider.getCanonicalName(); } // If the feed provider doesn't have a canonical name, use the component display name. if (canonicalName==null || canonicalName.isEmpty()) { canonicalName = component.getDisplayName(); } return canonicalName; } @Override public Object getObjectAt(int rowIndex, int columnIndex) { AbstractComponent component = structure.getValue(rowIndex, columnIndex); if (component == null) { return null; } else if (component == AbstractComponent.NULL_COMPONENT) { return ""; } else { String cellName = getObjectName(rowIndex, columnIndex); Object value = getValueForComponent(component); if (value instanceof DisplayedValue) { DisplayedValue dv = (DisplayedValue) value; TableCellSettings cellSettings = getCellSettings(getKey(component)); dv.setLabel(cellName!=null ? cellName : ""); dv.setAlignment(cellSettings.getAlignment()); dv.setNumberOfDecimals(cellSettings.getNumberOfDecimals()); } return value; } } private Object getValueForComponent(AbstractComponent component) { Object value = values.get(getKey(component)); if (value == null) { DisplayedValue displayedValue = new DisplayedValue(); if (component.getCapability(Placeholder.class) != null) { displayedValue.setValue(component.getCapability(Placeholder.class).getPlaceholderValue()); } else { displayedValue.setValue(component.getDisplayName()); } return displayedValue; } else { return value; } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } /** * Sets the value of an object updated by a data feed. This change * is propogated to all table cells displaying that object. * * @param id the identifier for the object updated * @param value the new value to display */ public void setValue(String id, Object value) { values.put(id, value); List<Point> locations = componentLocations.get(id); if (locations != null) { for (Point p : locations) { fireTableCellUpdated(p.y, p.x); } } } @Override public TableType getTableType() { return structure.getType(); } @Override protected boolean canSetObjectAt(int rowIndex, int columnIndex, boolean isInsertRow, boolean isInsertColumn) { return structure.canSetValue(rowIndex, columnIndex, isInsertRow, isInsertColumn); } @Override protected void setObjectAt(Object value, int rowIndex, int columnIndex, boolean isInsertRow, boolean isInsertColumn) { structure.setValue(rowIndex, columnIndex, isInsertRow, isInsertColumn, (AbstractComponent) value); updateLocations(); } @Override protected Object getStoredObjectAt(int objectIndex, int attributeIndex) { return structure.getValue(objectIndex, attributeIndex); } /** * Gets the component that will be modified to complete set a value at a particular * location. The new value may be inserted into a new row or column. * * @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 the component that must be modified to set the value at the indicated position */ public AbstractComponent getModifiedComponentAt(int rowIndex, int columnIndex, boolean isInsertRow, boolean isInsertColumn) { if (getOrientation() == TableOrientation.ROW_MAJOR) { return structure.getModifiedComponent(rowIndex, columnIndex, isInsertRow, isInsertColumn); } else { return structure.getModifiedComponent(columnIndex, rowIndex, isInsertColumn, isInsertRow); } } /** * Gets the cell settings for a particular component ID. * * @param id the identifier of the component in the cell * @return the cell settings, or a new set of default settings if no settings have been stored */ public TableCellSettings getCellSettings(String id) { TableCellSettings settings = cellSettings.get(id); if (settings == null) { settings = new TableCellSettings(); cellSettings.put(id, settings); } return settings; } /** * Notifies listeners that a table cell should be redrawn because its settings have changed. * * @param id the identifier for the component whose settings have changed */ public void fireCellSettingsChanged(String id) { List<Point> locations = componentLocations.get(id); if (locations != null) { for (Point p : locations) { fireTableCellUpdated(p.y, p.x); } } } /** * Gets the row label abbreviations for a specified row. * * @param row the row to retrieve abbreviations for * @return the abbreviations */ public LabelAbbreviations getRowLabelAbbreviations(int row) { LabelAbbreviations abbrevs = rowLabelAbbreviations.get(row); if (abbrevs != null) { return abbrevs; } else { return new LabelAbbreviations(); } } /** * Sets the row label abbreviations for a specified row. * * @param rowIndex the row to set abbreviations for * @param abbreviations the new abbreviations */ public void setRowLabelAbbreviations(int rowIndex, LabelAbbreviations abbreviations) { rowLabelAbbreviations.set(rowIndex, abbreviations); fireLabelsChanged(); } /** * Gets column label abbreviations for a specified column. * * @param column the column index to retrieve abbreviations for * @return the abbreviations */ public LabelAbbreviations getColumnLabelAbbreviations(int column) { LabelAbbreviations abbrevs = columnLabelAbbreviations.get(column); if (abbrevs != null) { return abbrevs; } else { return new LabelAbbreviations(); } } /** * Sets the column label abbreviations for a specified column. * * @param columnIndex the column to set abbreviations for * @param abbreviations the new abbreviations */ public void setColumnLabelAbbreviations(int columnIndex, LabelAbbreviations abbreviations) { columnLabelAbbreviations.set(columnIndex, abbreviations); fireLabelsChanged(); } /** * Indicates that the labels of the rows or columns have changed. */ public void fireHeaderLabelsChanged() { fireTableStructureChanged(); } /** * Gets the cell label abbreviations for a specific feed provider ID. * * @param id the feed provider ID (a PUI, if using ISP) * @return the cell label abbreviations */ public LabelAbbreviations getCellLabelAbbreviations(String id) { LabelAbbreviations abbrevs = cellLabelAbbreviations.get(id); if (abbrevs == null) { abbrevs = new LabelAbbreviations(); cellLabelAbbreviations.put(id, abbrevs); } return abbrevs; } /** * Sets the cell label abbreviations for a specific feed provider ID. * * @param id the feed provider ID (a PUI, if using ISP) * @param abbreviations the new cell label abbreviations */ public void setCellLabelAbbreviations(String id, LabelAbbreviations abbreviations) { cellLabelAbbreviations.put(id, abbreviations); } @Override public String getRowName(int rowIndex) { return abbreviateLabel(getFullRowName(rowIndex), getRowLabelAbbreviations(rowIndex)); } private String abbreviateLabel(String fullLabel, LabelAbbreviations abbrevs) { if (abbrevs == null) { return fullLabel; } else { return abbrevs.applyAbbreviations(fullLabel); } } @Override public String getColumnName(int columnIndex) { LabelAbbreviations x = getColumnLabelAbbreviations(columnIndex); return abbreviateLabel(getFullColumnName(columnIndex), getColumnLabelAbbreviations(columnIndex)); } @Override public String getCellName(int rowIndex, int columnIndex) { String fullLabel = super.getCellName(rowIndex, columnIndex); AbstractComponent component = (AbstractComponent) getStoredValueAt(rowIndex, columnIndex); if (component == null) { return fullLabel; } else { LabelAbbreviations abbrevs = getCellLabelAbbreviations(getKey(component)); return abbrevs.applyAbbreviations(fullLabel); } } @Override public void updateLabels() { super.updateLabels(); // Forget about any abbreviations we're no longer using. rowLabelAbbreviations.truncate(getRowCount()); columnLabelAbbreviations.truncate(getColumnCount()); } @Override public boolean isSkeleton() { return structure.isSkeleton(); } /** * Gets the maximum number of decimals shown in any cell in a column. * If we have already calculated the maximum for a column, returns it. * Otherwise iterates over all cells in the column to determine the * maximum, and remembers that value. * * @param columnIndex the column for which we want the max decimals setting * @return the maximum number of decimals shown in the column. */ public int getMaxDecimalsForColumn(int columnIndex) { Integer decimals = maxDecimalsForColumn.get(columnIndex); if (decimals != null) { return decimals; } int maxDecimals = 0; for (int rowIndex=0; rowIndex < getRowCount(); ++rowIndex) { AbstractComponent component = (AbstractComponent) getStoredValueAt(rowIndex, columnIndex); if (component != null) { TableCellSettings cellSettings = getCellSettings(getKey(component)); int cellDecimals = cellSettings.getNumberOfDecimals(); if (cellDecimals < 0) { cellDecimals = TableCellSettings.DEFAULT_DECIMALS; } maxDecimals = Math.max(maxDecimals, cellDecimals); } } maxDecimalsForColumn.set(columnIndex, maxDecimals); return maxDecimals; } /** * Updates the maximumd decimals for a column. For simplicity, * just forgets all maximum decimal settings. The max for each * column will be updated when {@link #getMaxDecimalsForColumn(int)} is * called by the table cell renderer. */ public void updateDecimalsForColumns() { maxDecimalsForColumn.clear(); } }