/*******************************************************************************
* 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 org.acme.example.view;
import gov.nasa.arc.mct.components.AbstractComponent;
import gov.nasa.arc.mct.components.FeedProvider;
import gov.nasa.arc.mct.components.FeedProvider.FeedType;
import gov.nasa.arc.mct.components.FeedProvider.RenderingInfo;
import gov.nasa.arc.mct.components.TimeConversion;
import gov.nasa.arc.mct.gui.FeedView;
import gov.nasa.arc.mct.gui.FeedView.RenderingCallback;
import gov.nasa.arc.mct.services.component.ViewInfo;
import java.awt.BorderLayout;
import java.awt.Color;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllegalFormatException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("serial")
public class MultiColView extends FeedView implements RenderingCallback {
private static final int DEFAULT_DECIMALS = 2;
private static final ContentAlignment DEFAULT_ALIGN = ContentAlignment.RIGHT;
private JTable table;
private MultiColTableModel model;
private TableSettingsControlPanel tableSettingsControlPanel;
private Map<String, TimeConversion> timeConversionMap = new HashMap<String, TimeConversion>();
private final AtomicReference<Collection<FeedProvider>> feedProvidersRef = new AtomicReference<Collection<FeedProvider>>(Collections.<FeedProvider>emptyList());
private static final DecimalFormat[] formats;
public static final String HIDDEN_COLUMNS_PROP = "HIDDEN_COLUMNS_PROP";
private static final Logger logger = LoggerFactory.getLogger(MultiColView.class);
static {
formats = new DecimalFormat[11];
formats[0] = new DecimalFormat("#");
String formatString = "#.";
for (int i = 1; i < formats.length; i++) {
formatString += "0";
DecimalFormat format = new DecimalFormat(formatString);
formats[i] = format;
}
}
public MultiColView(AbstractComponent ac, ViewInfo vi) {
super(ac,vi);
JPanel viewPanel = new JPanel(new BorderLayout());
ViewSettings settings = new ViewSettings();
AbstractComponent component = getManifestedComponent();
List<AbstractComponent> childrenList = component.getComponents();
//If no children, we display the selectedComponent.
if(childrenList.size()==0) {
childrenList = new ArrayList<AbstractComponent>();
childrenList.add(component);
}
//We ignore any components without feed providers
List<AbstractComponent> tempList = new ArrayList<AbstractComponent>();
for(AbstractComponent child : childrenList) {
if(child.getCapability(FeedProvider.class)!=null) {
tempList.add(child);
component.addViewManifestation(this);
}
}
childrenList = tempList;
model = new MultiColTableModel(childrenList, settings);
table = new JTable(model);
table.setAutoCreateRowSorter(true);
table.setShowGrid(false);
table.setFillsViewportHeight(true);
table.setBorder(BorderFactory.createEmptyBorder());
viewPanel.setBorder(BorderFactory.createEmptyBorder());
//We set up the cell and header renderers for each column.
MultiColColumnRenderer colHeaderRender = new MultiColColumnRenderer();
DynamicValueCellRender dynamicValueCellRender = new DynamicValueCellRender();
TimeCellRender timeCellRender = new TimeCellRender();
MultiColCellRenderer cellRender = new MultiColCellRenderer();
TableColumnModel colModel = table.getColumnModel();
ArrayList<ColumnType> colList = settings.getColumnTypes();
for(ColumnType colType : colList) {
colModel.getColumn(settings.getIndexForColumn(colType)).setHeaderRenderer(colHeaderRender);
if(colType==ColumnType.VALUE) {
colModel.getColumn(settings.getIndexForColumn(colType)).setCellRenderer(dynamicValueCellRender);
} else if(colType==ColumnType.TIME) {
colModel.getColumn(settings.getIndexForColumn(colType)).setCellRenderer(timeCellRender);
} else {
colModel.getColumn(settings.getIndexForColumn(colType)).setCellRenderer(cellRender);
}
}
viewPanel.add(table.getTableHeader(), BorderLayout.PAGE_START);
viewPanel.add(table, BorderLayout.CENTER);
setColorsToDefaults();
table.getColumnModel().setColumnMargin(1);
add(viewPanel, BorderLayout.NORTH);
updateFeedProviders();
tableSettingsControlPanel = new TableSettingsControlPanel(settings, table, this);
// Apply column show/hide states from view properties
Set<Object> hiddenColIds = getViewProperties().getProperty(HIDDEN_COLUMNS_PROP);
if (hiddenColIds != null && !hiddenColIds.isEmpty()) {
List<String> hiddenColIdList = new ArrayList<String>();
for (Object id : hiddenColIds) {
tableSettingsControlPanel.removeTableColumn(ColumnType.valueOf((String) id));
hiddenColIdList.add((String) id);
}
tableSettingsControlPanel.updateColumnVisibilityStates(hiddenColIdList);
}
}
private void setColorsToDefaults() {
Color bg = UIManager.getColor("TableViewManifestation.background");
setBackground(bg);
table.setBackground(bg);
bg = UIManager.getColor("TableViewManifestation.foreground");
table.setForeground(bg);
bg = UIManager.getColor("TableViewManifestation.header.background");
if(bg!=null) {
table.getTableHeader().setBackground(bg);
}
table.getTableHeader().setBorder(BorderFactory.createEmptyBorder());
Color defaultValueColor = UIManager.getColor("TableViewManifestation.defaultValueColor");
if(defaultValueColor!=null) {
table.getTableHeader().setForeground(defaultValueColor);
}
Color bgSelectionColor = UIManager.getColor("TableViewManifestation.selection.background");
if(bgSelectionColor!=null) {
table.setSelectionBackground(bgSelectionColor);
}
Color fgSelectionColor = UIManager.getColor("TableViewManifestation.selection.foreground");
if(fgSelectionColor!=null) {
table.setSelectionForeground(fgSelectionColor);
}
}
@Override
public void render(Map<String, List<Map<String, String>>> data) {
updateFromFeed(data);
}
@Override
public void updateFromFeed(Map<String, List<Map<String, String>>> data) {
if (data != null) {
Collection<FeedProvider> feeds = getVisibleFeedProviders();
for (FeedProvider provider : feeds) {
String feedId = provider.getSubscriptionId();
List<Map<String, String>> dataForThisFeed = data
.get(feedId);
if (dataForThisFeed != null && !dataForThisFeed.isEmpty()) {
Map<String, String> entry = dataForThisFeed
.get(dataForThisFeed.size() - 1);
try {
Object value = entry
.get(FeedProvider.NORMALIZED_VALUE_KEY);
RenderingInfo ri = provider.getRenderingInfo(entry);
if (provider.getFeedType() != FeedType.STRING) {
value = executeDecimalFormatter(provider,
value.toString(), data);
}
DisplayedValue displayedValue = new DisplayedValue();
displayedValue.setStatusText(ri.getStatusText());
displayedValue.setValueColor(ri.getValueColor());
displayedValue.setValue(ri.isValid() ? value
.toString() : "");
displayedValue.setNumberOfDecimals(DEFAULT_DECIMALS);
displayedValue.setAlignment(DEFAULT_ALIGN);
model.setValue(provider.getSubscriptionId(),displayedValue);
} catch (ClassCastException ex) {
logger.error("Feed data entry of unexpected type",ex);
} catch (NumberFormatException ex) {
logger.error("Feed data entry does not contain parsable value",ex);
}
}
}
} else {
logger.debug("Data was null");
}
}
/**
* Formats decimal places for the given value.
*
* @param value
* current value for the cell
* @return evaluated value
*/
private String executeDecimalFormatter(final FeedProvider provider,
final String feedValue,
final Map<String, List<Map<String, String>>> data) {
String rv = feedValue;
// Apply decimal places formatting if appropriate
FeedType feedType = provider.getFeedType();
int decimalPlaces = DEFAULT_DECIMALS;
if (feedType == FeedType.FLOATING_POINT
|| feedType == FeedType.INTEGER) {
if (decimalPlaces == -1) {
decimalPlaces = (feedType == FeedType.FLOATING_POINT) ? DEFAULT_DECIMALS: 0;
}
try {
rv = formats[decimalPlaces]
.format(FeedType.FLOATING_POINT
.convert(feedValue));
} catch (IllegalFormatException ife) {
logger.error("unable to format", ife);
} catch (NumberFormatException nfe) {
logger.error("unable to convert value to expected feed value",nfe);
}
}
return rv;
}
@Override
public void synchronizeTime(Map<String, List<Map<String, String>>> data,
long syncTime) {
updateFromFeed(data);
}
private void updateFeedProviders() {
ArrayList<FeedProvider> feedProviders = new ArrayList<FeedProvider>();
timeConversionMap.clear();
for (int rowIndex = 0; rowIndex < model.getRowCount(); ++rowIndex) {
AbstractComponent component = model.getComponentOfRow(rowIndex);
if(component!=null) {
FeedProvider fp = getFeedProvider(component);
if (fp != null) {
feedProviders.add(fp);
TimeConversion tc = component.getCapability(TimeConversion.class);
if (tc != null) {
timeConversionMap.put(fp.getSubscriptionId(), tc);
}
}
}
}
feedProviders.trimToSize();
feedProvidersRef.set(feedProviders);
}
@Override
public Collection<FeedProvider> getVisibleFeedProviders() {
return feedProvidersRef.get();
}
@Override
protected JComponent initializeControlManifestation() {
return tableSettingsControlPanel;
}
@Override
public void updateMonitoredGUI() {
// Update column visibility states
Set<String> colIdsToBeRemoved = getColumnIdsToBeRemoved();
if (!colIdsToBeRemoved.isEmpty()) {
for (String id : colIdsToBeRemoved) {
tableSettingsControlPanel.removeTableColumn(ColumnType.valueOf(id));
}
}
Set<String> colIdsToBeAdded = getColumnIdsToBeAdded();
if (!colIdsToBeAdded.isEmpty()) {
for (String id : colIdsToBeAdded) {
tableSettingsControlPanel.addTableColumn(ColumnType.valueOf(id));
}
}
Set<Object> hiddenColIds = getViewProperties().getProperty(HIDDEN_COLUMNS_PROP);
List<String> hiddenColIdList = new ArrayList<String>();
for (Object id : hiddenColIds)
hiddenColIdList.add((String) id);
tableSettingsControlPanel.updateColumnVisibilityStates(hiddenColIdList);
}
/**
* Returns the column ids to be removed from the current column model.
*/
private Set<String> getColumnIdsToBeRemoved() {
Set<String> colIdsToBeRemoved = new HashSet<String>();
Set<Object> hiddenColIds = getViewProperties().getProperty(HIDDEN_COLUMNS_PROP);
TableColumnModel columnModel = table.getColumnModel();
Enumeration<TableColumn> columns = columnModel.getColumns();
// Get the column ids to hide
while (columns.hasMoreElements()) {
TableColumn c = columns.nextElement();
for (Object hiddenColId : hiddenColIds) {
if (hiddenColId.equals(c.getIdentifier())) {
colIdsToBeRemoved.add((String) hiddenColId);
}
}
}
return colIdsToBeRemoved;
}
/**
* Returns the column ids to be added to the current column model.
*/
private Set<String> getColumnIdsToBeAdded() {
Set<String> colIdsToBeAdded = new HashSet<String>();
// Get the set of column ids that are visible
Set<Object> hiddenColIds = getViewProperties().getProperty(HIDDEN_COLUMNS_PROP);
for (ColumnType type : ColumnType.values()) {
boolean found = false;
for (Object id : hiddenColIds) {
if (id.equals(type.name()))
found = true;
}
if (!found)
colIdsToBeAdded.add(type.name());
}
// Remove the column ids that are already visible
TableColumnModel columnModel = table.getColumnModel();
Enumeration<TableColumn> columns = columnModel.getColumns();
while (columns.hasMoreElements()) {
TableColumn c = columns.nextElement();
if (colIdsToBeAdded.contains(c.getIdentifier()))
colIdsToBeAdded.remove(c.getIdentifier());
}
return colIdsToBeAdded;
}
}