/*******************************************************************************
* 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.graphics.view;
import gov.nasa.arc.mct.components.AbstractComponent;
import gov.nasa.arc.mct.components.FeedProvider;
import gov.nasa.arc.mct.components.FeedProvider.RenderingInfo;
import gov.nasa.arc.mct.evaluator.api.Evaluator;
import gov.nasa.arc.mct.graphics.brush.Brush;
import gov.nasa.arc.mct.graphics.state.StateSensitive;
import gov.nasa.arc.mct.gui.FeedView;
import gov.nasa.arc.mct.gui.FeedView.RenderingCallback;
import gov.nasa.arc.mct.gui.NamingContext;
import gov.nasa.arc.mct.gui.Request;
import gov.nasa.arc.mct.roles.events.PropertyChangeEvent;
import gov.nasa.arc.mct.services.component.ViewInfo;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;
/**
* A GraphicalManifestation provides a simple graphical view of an object
* (for instance, with a thermometer-style fill, or enumerated fill)
* @author vwoeltje
*/
public class GraphicalManifestation extends FeedView implements RenderingCallback {
private static ResourceBundle bundle = ResourceBundle.getBundle("GraphicsResourceBundle");
private static final long serialVersionUID = -5934962679343158613L;
private final List<FeedProvider> feedProviderList;
private GraphicalSettings settings;
private List<Brush> layers;
private Shape shape;
public static final String VIEW_ROLE_NAME = bundle.getString("View_Name");
public GraphicalManifestation(AbstractComponent component, ViewInfo vi) {
super(component,vi);
layers = new ArrayList<Brush>();
settings = new GraphicalSettings(this);
setBackground(UIManager.getColor("background"));
this.setOpaque(true);
feedProviderList = Collections.singletonList(getFeedProvider(getManifestedComponent()));
buildFromSettings();
}
@Override
protected JComponent initializeControlManifestation() {
return new JScrollPane(new GraphicalControlPanel(this),
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}
@Override
public void paint(Graphics g) {
boolean hasTitle = true;
NamingContext nc = getNamingContext();
if (nc != null && nc.getContextualName() == null) hasTitle = false;
/* Only paint background if we are on a panel or similar */
if (hasTitle) super.paint(g);
/* Pad for border, or other stuff */
Rectangle bounds = getBounds().getBounds();
int padding = Math.min(bounds.width, bounds.height) / 20;
bounds.grow(-padding, -padding);
/* Paint all layers in order */
for (Brush b : layers) {
b.draw(shape, g, bounds);
}
if (!hasTitle) paintDisplayName(g);
}
private void paintDisplayName(Graphics g) {
g.setFont(g.getFont().deriveFont(9.0f));
String name = getManifestedComponent().getDisplayName();
int width = g.getFontMetrics().stringWidth(name);
int height = g.getFontMetrics().getAscent() - 2;
int x = (getBounds().width - width) / 2;
int y = (getBounds().height) - height * 2;
g.setColor(new Color(255,255,255,160));
g.fillRoundRect(x - 4, y - 4, width + 8, height + 8, height / 2 , height / 2);
g.setColor(Color.DARK_GRAY);
g.drawString(name, x, y + height);
}
/**
* Rebuild display to reflect settings
*/
public void buildFromSettings() {
layers = settings.getLayers();
shape = (Shape) settings.getSetting(GraphicalSettings.GRAPHICAL_SHAPE);
requestPredictiveData();
}
/**
* Get the settings for this manifestation
* @return the settings for this manifestation
*/
public GraphicalSettings getSettings() {
return settings;
}
@Override
public Collection<FeedProvider> getVisibleFeedProviders() {
return feedProviderList;
}
@Override
public void synchronizeTime(Map<String, List<Map<String, String>>> data,
long syncTime) {
updateFromFeed(data);
}
@Override
public void updateFromFeed(Map<String, List<Map<String, String>>> data) {
AbstractComponent component = getManifestedComponent();
FeedProvider provider = getFeedProvider(component);
if (provider != null) {
List<Map<String, String>> feedData = data.get(provider.getSubscriptionId());
if (feedData != null && !feedData.isEmpty()) {
Map<String, String> feedDataItem = feedData.get(feedData.size() - 1);
List<RenderingInfo> riList = new ArrayList<RenderingInfo>();
riList.add(provider.getRenderingInfo(feedDataItem));
/* Get updates from the appropriate evaluator */
if (settings.getSetting(GraphicalSettings.GRAPHICAL_EVALUATOR) instanceof AbstractComponent) {
AbstractComponent comp = (AbstractComponent) settings.getSetting(GraphicalSettings.GRAPHICAL_EVALUATOR);
riList.add(comp.getCapability(Evaluator.class).evaluate(data, Collections.singletonList(provider)));
}
/* Send both numeric state and evaluator state to brushes */
if (riList.get(0).isPlottable()) { // ...only if we have new feed data
for (RenderingInfo ri : riList) {
for (Brush b : layers) {
if (b instanceof StateSensitive) {
((StateSensitive) b).setState(ri.getValueText());
}
}
}
}
// TODO: Show status character on LOS
}
}
repaint(); // Update the graphical representation
// TODO: Only repaint on change?
}
private void requestPredictiveData() {
/* Explicitly request data if we're using a predictive feed */
boolean predictive = false;
for (FeedProvider fp : feedProviderList) {
if (fp != null && fp.isPrediction()) {
predictive = true;
break;
}
}
if (predictive) {
requestData(feedProviderList, System.currentTimeMillis(), System.currentTimeMillis(),
new DataTransformation() {
@Override
public void transform(
Map<String, List<Map<String, String>>> data,
long startTime, long endTime) {}
},
this,
true);
}
/* We expect non-predictive feeds to push their data */
}
@Override
public void updateMonitoredGUI() {
this.buildFromSettings();
}
@Override
public void updateMonitoredGUI(PropertyChangeEvent evt) {
this.buildFromSettings();
// TODO: This may be where to catch naming context changes
}
@Override
public void render(Map<String, List<Map<String, String>>> data) {
updateFromFeed(data);
}
}