/*******************************************************************************
* 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.view;
import gov.nasa.arc.mct.components.AbstractComponent;
import gov.nasa.arc.mct.components.ExtendedProperties;
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.evaluator.api.Evaluator;
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.OptionBox;
import gov.nasa.arc.mct.gui.SelectionProvider;
import gov.nasa.arc.mct.gui.View;
import gov.nasa.arc.mct.policy.ExecutionResult;
import gov.nasa.arc.mct.policy.PolicyContext;
import gov.nasa.arc.mct.policy.PolicyInfo;
import gov.nasa.arc.mct.roles.events.AddChildEvent;
import gov.nasa.arc.mct.roles.events.PropertyChangeEvent;
import gov.nasa.arc.mct.roles.events.RemoveChildEvent;
import gov.nasa.arc.mct.services.component.PolicyManager;
import gov.nasa.arc.mct.services.component.ViewInfo;
import gov.nasa.arc.mct.services.component.ViewType;
import gov.nasa.arc.mct.table.access.ServiceAccess;
import gov.nasa.arc.mct.table.dnd.TableTransferHandler;
import gov.nasa.arc.mct.table.gui.LabeledTable;
import gov.nasa.arc.mct.table.gui.TableLayoutListener;
import gov.nasa.arc.mct.table.model.AbbreviatingTableLabelingAlgorithm;
import gov.nasa.arc.mct.table.model.ComponentTableModel;
import gov.nasa.arc.mct.table.model.TableOrientation;
import gov.nasa.arc.mct.table.model.TableStructure;
import gov.nasa.arc.mct.table.model.TableType;
import gov.nasa.arc.mct.table.policy.TableViewPolicy;
import gov.nasa.arc.mct.table.view.TableFormattingConstants.JVMFontFamily;
import gov.nasa.arc.mct.table.view.TimeFormat.DateFormatItem;
import java.awt.Color;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.DropMode;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements a visible manifestation of the the view role.
*/
@SuppressWarnings("serial")
public class TableViewManifestation extends FeedView
implements RenderingCallback, SelectionProvider {
private static final Logger logger = LoggerFactory.getLogger(TableViewManifestation.class);
private LabeledTable table;
private ComponentTableModel model;
private AbbreviatingTableLabelingAlgorithm labelingAlgorithm;
private Map<String, TimeConversion> timeConversionMap = new HashMap<String, TimeConversion>();
private final AtomicReference<Collection<FeedProvider>> feedProvidersRef = new AtomicReference<Collection<FeedProvider>>(Collections.<FeedProvider>emptyList());
private List<AbstractComponent> multipleEvaluators = new ArrayList<AbstractComponent>();
private static final DecimalFormat[] formats;
private boolean receivedData = false;
private boolean updating = false;
private TableSelectionListener selectionListener;
private TableSettingsControlPanel controlPanel;
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;
}
}
/**
* Creates a view manifestation for a component.
*
* If this is the first manifestation for a component, a new
* manifestation will be created with default settings. Subsequent
* manifestations will use the persisted properties.
*
* @param component
* the component for this manifestation
* @param vi the view information for this view
*/
public TableViewManifestation(AbstractComponent component, ViewInfo vi) {
super(component,vi);
labelingAlgorithm = new AbbreviatingTableLabelingAlgorithm();
setLabelingContext(labelingAlgorithm, getNamingContext());
TableStructure structure = TableViewPolicy
.getTableStructure(getManifestedComponent());
model = new ComponentTableModel(structure, labelingAlgorithm, this);
model.updateLabels();
TableViewCellRenderer renderer = new TableViewCellRenderer();
table = new LabeledTable(model);
table.getTable().setShowGrid(false);
table.getTable().getColumnModel().setColumnMargin(0);
table.getTable().setRowMargin(0);
table.getTable().setDefaultRenderer(Object.class,
renderer);
table.getTable().setTransferHandler(
new TableTransferHandler(this, table));
table.getTable().setDragEnabled(true);
table.getTable().setFillsViewportHeight(true);
if (structure.getType() == TableType.TWO_DIMENSIONAL) {
table.getTable().setDropMode(DropMode.ON_OR_INSERT);
} else {
table.getTable().setDropMode(DropMode.ON_OR_INSERT_ROWS);
}
Color bg = getColor("background");
setBackground(bg);
table.setBackground(bg);
table.getTable().setBackground(bg);
bg = getColor("header.background");
if (bg != null) {
table.getTable().getTableHeader().setBackground(bg);
table.getRowHeaders().setBackground(bg);
}
Color grid = getColor("grid");
if (grid != null) table.setHeaderBorder(BorderFactory.createLineBorder(grid, 0));
Color defaultValueColor = getColor("defaultValueColor");
if (defaultValueColor != null) {
renderer.setForeground(defaultValueColor);
table.getRowHeaders().setForeground(defaultValueColor);
table.getTable().getTableHeader().setForeground(defaultValueColor);
}
Color bgSelectionColor = getColor("selection.background");
Color fgSelectionColor = getColor("selection.foreground");
if (bgSelectionColor != null) {
table.getTable().setSelectionBackground(bgSelectionColor);
}
if (fgSelectionColor != null) {
table.getTable().setSelectionForeground(fgSelectionColor);
}
TableSettings tableSettings = loadSettingsFromPersistence();
table.setRestoringSettings(true);
table.updateColumnsFromModel(tableSettings!=null ? tableSettings.getColumnWidths() : null);
if (tableSettings != null) {
setTableSettings(tableSettings, table);
}
updateFeedProviders();
add(table);
table.setRestoringSettings(false);
table.updateColumnsHeaderValuesOnly();
table.addTableLayoutListener(new TableLayoutListener() {
@Override
public void tableChanged(Object source) {
model.notifyTableStructureChanged();
}
});
selectionListener = new TableSelectionListener();
// JTable has different listeners for row and column selections so doing one or the other will miss some change events.
table.getTable().getTableHeader().getColumnModel().getSelectionModel().addListSelectionListener(selectionListener);
table.getTable().getSelectionModel().addListSelectionListener(selectionListener);
table.getTable().addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getComponent().isEnabled()
&& e.getButton() == MouseEvent.BUTTON1
&& e.getClickCount() == 2) {
Point p = e.getPoint();
int row = table.getTable().rowAtPoint(p);
int column = table.getTable().columnAtPoint(p);
AbstractComponent ac = (AbstractComponent) model
.getStoredValueAt(row, column);
if (ac != null) {
ac.open();
}
}
}
});
addAncestorListener(new AncestorListener() {
@Override
public void ancestorAdded(AncestorEvent event) {
final TableViewManifestation table = TableViewManifestation.this;
Timer t = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
long startTime = Long.MIN_VALUE;
for (FeedProvider fp : getVisibleFeedProviders()) {
startTime = Math.max(startTime, fp
.getTimeService().getCurrentTime());
}
requestData(null, startTime, startTime, null, table, true);
}
});
t.setRepeats(false);
t.start();
removeAncestorListener(this);
}
@Override
public void ancestorMoved(AncestorEvent event) {
}
@Override
public void ancestorRemoved(AncestorEvent event) {
}
});
table.getTable().getColumnModel().addColumnModelListener(new TableColumnModelListener() {
@Override
public void columnAdded(TableColumnModelEvent e) {
}
@Override
public void columnMarginChanged(ChangeEvent e) {
boolean marginChanged = false;
for (int i = 0; i < table.getTable().getColumnCount() ; i++) {
if (getViewProperties().hasProperty() && (table.getTable().getColumnModel().getColumn(i).getWidth() != table.getColumnWidths()[i])) {
marginChanged = true;
}
}
if (marginChanged) {
saveCellSettings();
if (controlPanel != null) {
controlPanel.loadSettings();
}
}
}
@Override
public void columnMoved(TableColumnModelEvent e) {
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
}
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
});
}
private Color getColor(String name) {
return UIManager.getColor(name);
}
@Override
protected void handleNamingContextChange() {
updateMonitoredGUI();
}
/**
* Set the surrounding context labels that should be removed when
* calculating labels for the rows, columns, and cells of the table. The
* surrounding context consists of the panel name in which the table is
* embedded. This may be set in the manifest information or may be the
* collection object name, if the panel title is not customized.
* Specifically, if the panel title is visible and non-empty, that is
* used as the labeling context (even if just blanks). If the panel
* title is invisible, the labeling context is empty. Otherwise, the
* labeling context is the component's display name.
*
* @param algorithm
* the table labeling algorithm
* @param context
* to derive labels from
*/
private void setLabelingContext(
AbbreviatingTableLabelingAlgorithm algorithm,
NamingContext context) {
String surroundingName = "";
if (context != null) {
/* Is some name being shown by the labeling context? */
if (context.getContextualName() != null) {
surroundingName = context.getContextualName(); /* Get that name. */
if (surroundingName.isEmpty()) {
/* A title bar or similar is displayed, but it's not overriding our *
* base displayed name */
surroundingName = getManifestedComponent().getDisplayName();
}
}
} else {
/* Labeling context is null, so we are in our own window or inspector */
surroundingName = getManifestedComponent().getDisplayName();
}
algorithm.setContextLabels(surroundingName);
}
@Override
protected JComponent initializeControlManifestation() {
TableControlPanelController controller = new TableControlPanelController(
this, table, model);
controlPanel = new TableSettingsControlPanel(controller);
return controlPanel;
}
@Override
public SelectionProvider getSelectionProvider() {
return this;
}
@Override
public void removeSelectionChangeListener(
PropertyChangeListener listener) {
removePropertyChangeListener(
SelectionProvider.SELECTION_CHANGED_PROP, listener);
}
@Override
public void addSelectionChangeListener(PropertyChangeListener listener) {
addPropertyChangeListener(SelectionProvider.SELECTION_CHANGED_PROP,
listener);
}
@Override
public Collection<View> getSelectedManifestations() {
List<View> selectedManifestations = new ArrayList<View>();
for (int row : table.getTable().getSelectedRows()) {
for (int col : table.getTable().getSelectedColumns()) {
AbstractComponent component = (AbstractComponent) model
.getStoredValueAt(row, col);
if (component != null
&& component != AbstractComponent.NULL_COMPONENT) {
selectedManifestations.add(component.getViewInfos(ViewType.NODE).iterator().next().createView(component));
}
}
}
return selectedManifestations;
}
private boolean isClearingSelection = false;
private boolean orientationChanged = false;
public static final String VIEW_ROLE_NAME = "Alpha";
@Override
public void clearCurrentSelections() {
isClearingSelection = true;
try {
table.getTable().clearSelection();
selectionListener.clearLastSelectedComponents();
} finally {
isClearingSelection = false;
}
}
@Override
public void updateMonitoredGUI(AddChildEvent event) {
recreateTable();
}
/**
* Recreates the columns and rows of the table so the on-screen
* representation matches the current state of the model. First
* notifies the model that the underlying components may have
* changed (so that the model will interrogate the components for
* the current number of rows and columns). Then updates the set
* of feed providers to match the model and updates the labels by
* running the labeling algorithm. Afterwards, the columns in the
* table are recreated, in case the number of columns has changed.
* Finally, a table-structure-changed event is fired to force
* complete redisplay of the table on-screen.
*/
private void recreateTable() {
//TODO: Recreate without clobbering user selections (except where appropriate)
model.notifyTableStructureChanged();
updateFeedProviders();
model.updateLabels();
table.updateColumnsFromModel(null);
table.updateColumnsHeaderValuesOnly();
}
@Override
public void updateMonitoredGUI(RemoveChildEvent event) {
recreateTable();
}
@Override
public void updateMonitoredGUI(PropertyChangeEvent event) {
// Display name or panel title has changed. Must recalculate labels.
setLabelingContext(labelingAlgorithm, getNamingContext());
recreateTable();
}
@Override
public Collection<FeedProvider> getVisibleFeedProviders() {
return feedProvidersRef.get();
}
@Override
public void synchronizeTime(
Map<String, List<Map<String, String>>> data, long syncTime) {
updateFromFeed(data);
}
@Override
public void render(Map<String, List<Map<String, String>>> data) {
if (!receivedData) {
updateFromFeed(data);
}
}
/**
* Extract data from the feed and push it to the table.
*/
@Override
public void updateFromFeed(Map<String, List<Map<String, String>>> data) {
receivedData = true;
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()) {
// Process the first value for this feed.
Map<String, String> entry = dataForThisFeed
.get(dataForThisFeed.size() - 1);
try {
Object value = entry
.get(FeedProvider.NORMALIZED_VALUE_KEY);
RenderingInfo ri = provider.getRenderingInfo(entry);
TableCellSettings settings = model
.getCellSettings(provider
.getSubscriptionId());
if (settings.getEvaluator() != null) {
ri = settings
.getEvaluator()
.getCapability(Evaluator.class)
.evaluate(
data,
Collections
.singletonList(provider));
value = ri.getValueText();
} else {
if (provider.getFeedType() != FeedType.STRING) {
if (settings.getDateFormat() != null && settings.getDateFormat() != DateFormatItem.None) {
TimeConversion tc = timeConversionMap.get(provider.getSubscriptionId());
value = TimeFormat.applySimpleDateFormat(settings.getDateFormatter(),
tc, value.toString());
} else {
value = executeDecimalFormatter(provider,
value.toString(), data, settings);
}
}
}
DisplayedValue displayedValue = new DisplayedValue();
displayedValue.setStatusText(ri.getStatusText());
displayedValue.setValueColor(ri.getValueColor());
if (ri.getStatusText().isEmpty() || ri.getStatusText().equals(" ")) {
if (settings.getFontColor() != null) {
displayedValue.setValueColor(settings.getFontColor());
}
}
// Set color according to font color settings, as long as value is valid
displayedValue.setValue(ri.isValid() ? value
.toString() : "");
displayedValue.setNumberOfDecimals(settings
.getNumberOfDecimals());
displayedValue
.setAlignment(settings.getAlignment());
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);
}
}
}
// execute multiple evaluators to ensure their values are
// updated also
for (AbstractComponent multi : multipleEvaluators) {
Evaluator evaluator = multi.getCapability(Evaluator.class);
FeedProvider.RenderingInfo info = evaluator.evaluate(data,
getFeedProviders(multi));
if (info != null && info.getValueText() != null) {
TableCellSettings settings = model.getCellSettings(model
.getKey(multi));
DisplayedValue displayedValue = new DisplayedValue();
displayedValue.setValue(info.getValueText());
displayedValue.setStatusText(info.getStatusText());
displayedValue.setValueColor(info.getValueColor());
displayedValue.setNumberOfDecimals(settings
.getNumberOfDecimals());
displayedValue.setAlignment(settings.getAlignment());
assert !info.getValueColor().equals(Color.white) : "attempting to rendering white text on white foreground";
if (info.getStatusText().isEmpty() || info.getStatusText().equals(" ")) {
if (settings.getFontColor() != null) {
displayedValue.setValueColor(settings.getFontColor());
}
}
model.setValue(model.getKey(multi), displayedValue);
}
}
} else {
logger.debug("Data was null");
}
}
private List<FeedProvider> getFeedProviders(AbstractComponent component) {
List<FeedProvider> feedProviders = new ArrayList<FeedProvider>(
component.getComponents().size());
for (AbstractComponent referencedComponent : component.getComponents()) {
FeedProvider fp = referencedComponent.getCapability(
FeedProvider.class);
if (fp != null) {
feedProviders.add(fp);
}
}
return feedProviders;
}
/**
* 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,
TableCellSettings cellSettings) {
String rv = feedValue;
// apply decimal places formatting if appropriate
FeedType feedType = provider.getFeedType();
int decimalPlaces = cellSettings.getNumberOfDecimals();
if (feedType == FeedType.FLOATING_POINT
|| feedType == FeedType.INTEGER) {
if (decimalPlaces == -1) {
decimalPlaces = (feedType == FeedType.FLOATING_POINT) ? TableCellSettings.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;
}
private void updateFeedProviders() {
TableStructure structure = TableViewPolicy
.getTableStructure(getManifestedComponent());
updateFeedProviders(structure);
updateEvalutors();
loadHeaderSettings();
loadCellSettings();
}
private void loadHeaderSettings() {
for (int row = 0; row < model.getRowCount(); ++row) {
String labelAbbreviations = getViewProperties()
.getProperty("ROW_LABEL_ABBREVIATIONS_" + row, String.class);
if (labelAbbreviations != null) {
LabelAbbreviations abbrevs = new LabelAbbreviations();
abbrevs.addAbbreviationsFromString(labelAbbreviations);
model.setRowLabelAbbreviations(row, abbrevs);
}
}
for (int col = 0; col < model.getColumnCount(); ++col) {
String labelAbbreviations = getViewProperties()
.getProperty("COLUMN_LABEL_ABBREVIATIONS_" + col, String.class);
if (labelAbbreviations != null) {
LabelAbbreviations abbrevs = new LabelAbbreviations();
abbrevs.addAbbreviationsFromString(labelAbbreviations);
model.setColumnLabelAbbreviations(col, abbrevs);
}
}
}
private boolean saveHeaderSettings(boolean orientationChanged) {
boolean settingsChanged = orientationChanged;
ExtendedProperties viewProperties = getViewProperties();
for (int row = 0; row < model.getRowCount(); ++row) {
String oldLabelAbbreviations = getViewProperties()
.getProperty("ROW_LABEL_ABBREVIATIONS_" + row, String.class);
String newLabelAbbreviations = model.getRowLabelAbbreviations(
row).toString();
if (orientationChanged || !newLabelAbbreviations.equals(oldLabelAbbreviations)) {
viewProperties.setProperty(
"ROW_LABEL_ABBREVIATIONS_" + row,
newLabelAbbreviations);
settingsChanged = true;
}
}
for (int col = 0; col < model.getColumnCount(); ++col) {
String oldLabelAbbreviations = getViewProperties()
.getProperty("COLUMN_LABEL_ABBREVIATIONS_" + col, String.class);
String newLabelAbbreviations = model
.getColumnLabelAbbreviations(col).toString();
if (orientationChanged || !newLabelAbbreviations.equals(oldLabelAbbreviations)) {
viewProperties.setProperty(
"COLUMN_LABEL_ABBREVIATIONS_" + col,
newLabelAbbreviations);
settingsChanged = true;
}
}
orientationChanged = false;
return settingsChanged;
}
private void loadCellSettings() {
for (int row = 0; row < model.getRowCount(); ++row) {
for (int col = 0; col < model.getColumnCount(); ++col) {
AbstractComponent component = (AbstractComponent) model
.getStoredValueAt(row, col);
if (component != null) {
loadCellSettings(component);
}
}
}
}
/**
* Extracts the evaluators from the set of components. An evaluator is
* associated with a component if the evaluator references only that
* component.
*/
private void updateEvalutors() {
multipleEvaluators.clear();
for (AbstractComponent component : getManifestedComponent().getComponents()) {
addMultisOneLevel(component);
addMultis(multipleEvaluators, component);
}
addMultis(multipleEvaluators, getManifestedComponent());
}
private void addMultis(List<AbstractComponent> multis,
AbstractComponent component) {
if (isEvaluator(component)
&& component.getCapability(Evaluator.class)
.requiresMultipleInputs()) {
multis.add(component);
}
}
private void addMultisOneLevel(AbstractComponent root) {
for (AbstractComponent component : root.getComponents()) {
if (isEvaluator(component) && component.getCapability(Evaluator.class).requiresMultipleInputs()) {
addMultis(multipleEvaluators, component);
}
}
}
private boolean isEvaluator(AbstractComponent comp) {
return comp.getCapability(Evaluator.class) != null;
}
private void updateFeedProviders(TableStructure structure) {
ArrayList<FeedProvider> feedProviders = new ArrayList<FeedProvider>();
timeConversionMap.clear();
for (int rowIndex = 0; rowIndex < structure.getRowCount(); ++rowIndex) {
for (int columnIndex = 0; columnIndex < structure
.getColumnCount(); ++columnIndex) {
AbstractComponent component = structure.getValue(rowIndex,
columnIndex);
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);
}
} else {
if (component.getCapability(Evaluator.class) != null) {
for (AbstractComponent referencedComponent : component.getComponents()) {
fp = getFeedProvider(referencedComponent);
if (fp != null) {
feedProviders.add(fp);
}
}
}
}
}
}
}
feedProviders.trimToSize();
feedProvidersRef.set(feedProviders);
}
// Update the view manifestation when the manifestation info has
// changed in the database.
@Override
public void updateMonitoredGUI() {
if (!updating) {
setTableSettings(loadSettingsFromPersistence(), table);
recreateTable();
}
}
@Override
public void clear(Collection<FeedProvider> feedProviders) {
for (FeedProvider provider : feedProviders) {
model.setValue(provider.getSubscriptionId(), "");
}
}
/**
* Load the settings for the manifestation from persistence.
*
* @return
*/
TableSettings loadSettingsFromPersistence() {
TableSettings settings = new TableSettings();
for (TableSettings.AvailableSettings setting : TableSettings.AvailableSettings
.values()) {
try {
String name = setting.name();
String value = getViewProperties().getProperty(name, String.class);
settings.setValue(setting, value);
} catch (Exception ex) {
logger.error("exception when loading persistent settings", ex);
// ignore - no persisted setting
}
}
return settings;
}
private void loadCellSettings(AbstractComponent component) {
String id = model.getKey(component);
TableCellSettings cellSettings = model.getCellSettings(id);
String value = getViewProperties().getProperty(
"EVALUATOR_" + id, String.class);
if (value == null || value.isEmpty()) {
cellSettings.setEvaluator(null);
} else {
cellSettings.setEvaluator(findEvaluator(component, value));
}
LabelAbbreviations abbreviations = new LabelAbbreviations();
value = getViewProperties().getProperty(
"LABEL_ABBREVIATIONS_" + id, String.class);
if (value != null && !value.isEmpty()) {
abbreviations.addAbbreviationsFromString(value);
}
model.setCellLabelAbbreviations(id, abbreviations);
value = getViewProperties().getProperty("DECIMAL_PLACES_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setNumberOfDecimals(Integer.valueOf(value));
}
value = getViewProperties().getProperty("FONT_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setCellFont(Enum.valueOf(TableFormattingConstants.JVMFontFamily.class, value));
}
value = getViewProperties().getProperty("FONT_COLOR_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setFontColor(new Color(Integer.valueOf(value).intValue()));
}
value = getViewProperties().getProperty("BG_COLOR_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setBackgroundColor(new Color(Integer.valueOf(value).intValue()));
}
value = getViewProperties().getProperty("FONT_SIZE_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setFontSize(Integer.valueOf(value).intValue());
}
value = getViewProperties().getProperty("FONT_STYLE_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setFontStyle(Integer.valueOf(value).intValue());
}
value = getViewProperties().getProperty("AS_DATE_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setDateFormat(DateFormatItem.valueOf(value));
}
value = getViewProperties().getProperty("ALIGNMENT_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setAlignment(ContentAlignment.valueOf(value));
}
value = getViewProperties().getProperty("BORDER_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setCellBorderState(new BorderState(value));
}
value = getViewProperties().getProperty("FONT_UNDERLINE_" + id, String.class);
if (value != null && !value.isEmpty()) {
cellSettings.setTextAttributeUnderline(Integer.valueOf(value).intValue());
}
}
private AbstractComponent findEvaluator(AbstractComponent component,
String id) {
// The component may be its own evaluator.
if (id.equals(component.getId())) {
return component;
}
AbstractComponent parent = AbstractComponent.getComponentById(id);
Evaluator e = parent == null ? null : parent.getCapability(Evaluator.class);
if (e != null && !e.requiresMultipleInputs()
&& parent.getId().equals(id)) {
return parent;
}
return null;
}
private void saveSettingsToPersistence() {
boolean settingsChanged = false;
TableSettings settings = getCurrentTableSettings(table);
ExtendedProperties viewProperties = getViewProperties();
for (TableSettings.AvailableSettings setting : TableSettings.AvailableSettings
.values()) {
String currentValue = getViewProperties().getProperty(
setting.name(), String.class);
String newValue = settings.getValue(setting);
assert (newValue != null) : "Table setting for "
+ setting.toString() + " has null value";
if (!newValue.equals(currentValue)) {
viewProperties.setProperty(setting.name(),
newValue);
settingsChanged = true;
}
}
// Save label settings.
settingsChanged = (saveHeaderSettings(orientationChanged) || settingsChanged);
// Also save all cell settings.
for (int row = 0; row < model.getRowCount(); ++row) {
for (int col = 0; col < model.getColumnCount(); ++col) {
AbstractComponent component = (AbstractComponent) model
.getStoredValueAt(row, col);
if (component != null) {
settingsChanged = (saveCellSettings(component) || settingsChanged);
}
}
}
if (settingsChanged) {
try {
updating = true;
table.updateColumnsHeaderValuesOnly();
getManifestedComponent().save();
} finally {
updating = false;
}
}
}
private boolean saveCellSettings(AbstractComponent component) {
boolean settingsChanged = false;
String id = model.getKey(component);
TableCellSettings cellSettings = model.getCellSettings(id);
String propertyName = "EVALUATOR_" + id;
String currentValue = getViewProperties().getProperty(
propertyName, String.class);
ExtendedProperties viewProperties = getViewProperties();
if (currentValue == null) {
currentValue = "";
}
String newValue = (cellSettings.getEvaluator() == null) ? ""
: cellSettings.getEvaluator().getId();
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
LabelAbbreviations abbrevs = model.getCellLabelAbbreviations(id);
propertyName = "LABEL_ABBREVIATIONS_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = abbrevs.toString();
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "DECIMAL_PLACES_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = Integer.toString(cellSettings.getNumberOfDecimals());
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "ALIGNMENT_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = cellSettings.getAlignment().toString();
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "BORDER_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = cellSettings.getCellBorderState().toString();
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "AS_DATE_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = cellSettings.getDateFormat().toString();
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "FONT_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = cellSettings.getCellFont().name();
newValue = (newValue == null ? "" : newValue);
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "FONT_SIZE_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = String.valueOf(cellSettings.getFontSize());
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "FONT_COLOR_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = (cellSettings.getForegroundColor() != null ?
String.valueOf(cellSettings.getForegroundColor().getRGB()) : "");
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "BG_COLOR_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = (cellSettings.getBackgroundColor() != null ?
String.valueOf(cellSettings.getBackgroundColor().getRGB()) : "");
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "FONT_STYLE_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = String.valueOf(cellSettings.getFontStyle());
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
propertyName = "FONT_UNDERLINE_" + id;
currentValue = getViewProperties().getProperty(propertyName, String.class);
if (currentValue == null) {
currentValue = "";
}
newValue = String.valueOf(cellSettings.getTextAttributeUnderline());
if (!currentValue.equals(newValue)) {
viewProperties.setProperty(propertyName, newValue);
settingsChanged = true;
}
return settingsChanged;
}
private void setTableSettings(TableSettings settings, LabeledTable table) {
TableOrientation orientation = settings.getOrientation();
int[] columnWidths = settings.getColumnWidths();
int[] columnOrder = settings.getColumnOrder();
int[] rowHeights = settings.getRowHeights();
ContentAlignment[] rowHeaderAlignments = settings
.getRowHeaderAlignments();
ContentAlignment[] columnHeaderAlignments = settings
.getColumnHeaderAlignments();
JVMFontFamily[] rowFontNames = settings.getRowFontNames();
int[] rowFontColors = settings.getRowFontColors();
int[] rowBackgroundColors = settings.getRowBackgroundColors();
int[] rowHeaderBorderColors = settings.getRowHeaderBorderColors();
int[] rowFontSizes = settings.getRowFontSizes();
int[] rowFontStyles = settings.getRowFontStyles();
int[] rowTextAttributes = settings.getRowHeaderTextAttributes();
BorderState[] rowHeaderBorderStates = settings.getRowHeaderBorderStates();
BorderState[] columnHeaderBorderStates = settings.getColumnHeaderBorderStates();
JVMFontFamily[] colFontNames = settings.getColumnFontNames();
int[] columnFontColors = settings.getColumnFontColors();
int[] columnBackgroundColors = settings.getColumnBackgroundColors();
int[] columnHeaderBorderColors = settings.getColumnHeaderBorderColors();
int[] colFontSizes = settings.getColumnFontSizes();
int[] colFontStyles = settings.getColumnFontStyles();
int[] columnTextAttributes = settings.getColumnHeaderTextAttributes();
if (orientation != null) {
labelingAlgorithm.setOrientation(orientation);
model.setOrientation(orientation);
table.updateDropMode();
}
// Set the column order first, so that the right values get the
// right
// width once we set the widths.
if (columnOrder != null) {
table.setColumnOrder(columnOrder);
}
if (columnWidths != null) {
table.setColumnWidths(columnWidths);
}
if (rowHeights != null) {
table.setRowHeights(rowHeights);
}
if (rowHeaderAlignments != null) {
table.setRowHeaderAlignments(rowHeaderAlignments);
}
if (columnHeaderAlignments != null) {
table.setColumnHeaderAlignments(columnHeaderAlignments);
}
if (rowFontNames != null) {
table.setRowHeaderFontNames(rowFontNames);
}
if (rowFontColors != null) {
table.setRowHeaderFontColors(intToColorArray(rowFontColors));
}
if (rowBackgroundColors != null) {
table.setRowHeaderBackgroundColors(intToColorArray(rowBackgroundColors));
}
if (rowHeaderBorderColors != null) {
table.setRowHeaderBorderColors(intToColorArray(rowHeaderBorderColors));
}
if (rowFontSizes != null) {
table.setRowHeaderFontSizes(intToIntegerArray(rowFontSizes));
}
if (rowFontStyles != null) {
table.setRowHeaderFontStyles(intToIntegerArray(rowFontStyles));
}
if (rowTextAttributes != null) {
table.setRowHeaderTextAttributes(intToIntegerArray(rowTextAttributes));
}
if (rowHeaderBorderStates != null) {
table.setRowHeaderBorderStates(rowHeaderBorderStates);
}
if (colFontNames != null) {
table.setColumnHeaderFontNames(colFontNames);
}
if (columnFontColors != null) {
table.setColumnHeaderFontColors(intToColorArray(columnFontColors));
}
if (columnBackgroundColors != null) {
table.setColumnHeaderBackgroundColors(intToColorArray(columnBackgroundColors));
}
if (columnHeaderBorderColors != null) {
table.setColumnHeaderBorderColors(intToColorArray(columnHeaderBorderColors));
}
if (colFontSizes != null) {
table.setColumnHeaderFontSizes(intToIntegerArray(colFontSizes));
}
if (colFontStyles != null) {
table.setColumnHeaderFontStyles(intToIntegerArray(colFontStyles));
}
if (columnTextAttributes != null) {
table.setColumnHeaderTextAttributes(intToIntegerArray(columnTextAttributes));
}
if (columnHeaderBorderStates != null) {
table.setColumnHeaderBorderStates(columnHeaderBorderStates);
}
table.getTable().setShowGrid(settings.isShowGrid());
}
private TableSettings getCurrentTableSettings(LabeledTable table) {
TableSettings settings = new TableSettings();
settings.setOrientation(model.getOrientation());
settings.setColumnWidths(table.getColumnWidths());
settings.setColumnOrder(table.getColumnOrder());
settings.setShowGrid(table.getShowGrid());
settings.setRowHeights(integerToIntArray(table.getRowHeights()));
settings.setRowHeaderAlignments(table.getRowHeaderAlignments());
settings.setColumnHeaderAlignments(table
.getColummnHeaderAlignments());
settings.setRowFontNames(table.getRowHeaderFontNames());
settings.setRowFontColors(colorToIntArray(table.getRowHeaderFontColors()));
settings.setRowHeaderBorderColors(colorToIntArray(table.getRowHeaderBorderColors()));
settings.setRowBackgroundColors(colorToIntArray(table.getRowHeaderBackgroundColors()));
settings.setRowFontSizes(integerToIntArray(table.getRowHeaderFontSizes()));
settings.setRowFontStyles(integerToIntArray(table.getRowHeaderFontStyles()));
settings.setRowTextAttributes(integerToIntArray(table.getRowHeaderTextAttributes()));
settings.setColumnFontNames(table.getColumnHeaderFontNames());
settings.setColumnFontColors(colorToIntArray(table.getColumnHeaderFontColors()));
settings.setColumnBackgroundColors(colorToIntArray(table.getColumnHeaderBackgroundColors()));
settings.setColumnHeaderBorderColors(colorToIntArray(table.getColumnHeaderBorderColors()));
settings.setColumnFontSizes(integerToIntArray(table.getColumnHeaderFontSizes()));
settings.setColumnFontStyles(integerToIntArray(table.getColumnHeaderFontStyles()));
settings.setColumnTextAttributes(integerToIntArray(table.getColumnHeaderTextAttributes()));
settings.setRowHeaderBorderStates(table.getRowHeaderBorderStates());
settings.setColumnHeaderBorderStates(table.getColumnHeaderBorderStates());
return settings;
}
private Color[] intToColorArray(int[] a) {
Color[] newArray = new Color[a.length];
for (int i = 0; i < a.length ; i++) {
newArray[i] = new Color(a[i]);
}
return newArray;
}
private int[] colorToIntArray(Color[] a) {
int[] newArray = new int[a.length];
for (int i = 0; i < a.length ; i++) {
newArray[i] = a[i].getRGB();
}
return newArray;
}
private int[] integerToIntArray(Integer[] a) {
int[] newArray = new int[a.length];
for (int i = 0; i < a.length ; i++) {
newArray[i] = a[i].intValue();
}
return newArray;
}
private Integer[] intToIntegerArray(int[] a) {
Integer[] newArray = new Integer[a.length];
for (int i = 0; i < a.length ; i++) {
newArray[i] = Integer.valueOf(a[i]);
}
return newArray;
}
/**
* Save the cell settings when they are changed in the formatting panel.
*/
public void saveCellSettings() {
saveSettingsToPersistence();
}
/**
* Save the cell settings when table orientation is changed in the formatting panel.
*/
public void saveCellSettingsUponTableOrientationChange() {
orientationChanged = true;
saveSettingsToPersistence();
}
/**
* Performs the object interactions required to drop an object into the
* table.
*
* @param sourceViews
* an array of view roles for the components dropped onto the
* table
* @param row
* the row at which the drop occurred
* @param column
* the column at which the drop occurred
* @param isInsertRow
* true, if we should insert a row at the drop position
* @param isInsertColumn
* true, if we should insert a column at the drop position
* @return true, if the drop was successful
*/
public boolean handleDrop(View[] sourceViews, int row,
int column, boolean isInsertRow, boolean isInsertColumn) {
if (sourceViews.length > 0) {
AbstractComponent component = sourceViews[0].getManifestedComponent();
Collection<AbstractComponent> sourceComponents = Collections
.singleton(component);
AbstractComponent targetComponent = model
.getModifiedComponentAt(row, column, isInsertRow,
isInsertColumn);
final ExecutionResult result = checkDropPolicy(targetComponent,
sourceComponents, this);
if (result.getStatus()) {
model.setValueAt(component, row, column, isInsertRow,
isInsertColumn);
recreateTable();
} else {
// Action is _not_ permitted under policy constraint
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Inform the user that policy prohibited the
// operation.
OptionBox.showMessageDialog(
TableViewManifestation.this,
result.getMessage(),
"Composition Error - ",
OptionBox.ERROR_MESSAGE);
}
});
}
}
return true;
}
private ExecutionResult checkDropPolicy(
AbstractComponent targetComponent,
Collection<AbstractComponent> sourceComponents,
View targetViewManifesation) {
// Establish policy context.
PolicyContext context = new PolicyContext();
context.setProperty(
PolicyContext.PropertyName.TARGET_COMPONENT.getName(),
targetComponent);
context.setProperty(
PolicyContext.PropertyName.SOURCE_COMPONENTS.getName(),
sourceComponents);
context.setProperty(PolicyContext.PropertyName.ACTION.getName(),
Character.valueOf('w'));
context.setProperty(
PolicyContext.PropertyName.VIEW_MANIFESTATION_PROVIDER
.getName(), targetViewManifesation);
String compositionKey = PolicyInfo.CategoryType.COMPOSITION_POLICY_CATEGORY
.getKey();
// Execute policy
return ServiceAccess.getService(PolicyManager.class).execute(
compositionKey, context);
}
/**
* Gets abbreviation view properties from persistence. Does a swap of row/col upon a table orientation
* and persists. Also updates the the new properties in the model. Uses the inclusive
* square so that persisted settings for non-square tables are removed.
* @param orientation the new table orientation
*/
public void swapAbbreviationsUponTableOrientationChange(TableOrientation orientation) {
int inclusiveSquare = Math.max(model.getRowCount(), model.getColumnCount());
String[] rowAbbrevProperties = new String[inclusiveSquare];
String[] colAbbrevProperties = new String[inclusiveSquare];
// get all the row and col headers
for (int i = 0; i < inclusiveSquare; i++) {
rowAbbrevProperties[i] = getViewProperties().getProperty("ROW_LABEL_ABBREVIATIONS_" + i, String.class);
}
for (int j = 0; j < inclusiveSquare; j++) {
colAbbrevProperties[j] = getViewProperties().getProperty("COLUMN_LABEL_ABBREVIATIONS_" + j, String.class);
}
//swap for persistence. Also update the model.
String labelAbbreviations = null;
for (int i = 0; i < inclusiveSquare; i++) {
labelAbbreviations = rowAbbrevProperties[i];
if (labelAbbreviations == null) labelAbbreviations = "";
// rather than setting view properties here, we let saveHeaderSettings() set view properties.
LabelAbbreviations abbrevs = new LabelAbbreviations();
abbrevs.addAbbreviationsFromString(labelAbbreviations);
model.setColumnLabelAbbreviations(i, abbrevs);
}
for (int j = 0; j < inclusiveSquare; j++) {
labelAbbreviations = colAbbrevProperties[j];
if (labelAbbreviations == null) labelAbbreviations = "";
LabelAbbreviations abbrevs = new LabelAbbreviations();
abbrevs.addAbbreviationsFromString(labelAbbreviations);
model.setRowLabelAbbreviations(j, abbrevs);
}
}
/**
* @author dcberrio
* A ListSelectionListener that caches the components selected in a table, in
* order to conflate row and column change events
*/
public class TableSelectionListener implements ListSelectionListener {
/**
* keep track of the last selected set of components this is done to eliminate duplicate events when
* both the row and column have changed, since this would result in two events.
*/
private Set<AbstractComponent> lastSelectedComponents = new HashSet<AbstractComponent>();
private boolean lastSelectedComponentsAreSame(Collection<View> selectedManifestations) {
Set<AbstractComponent> currentSelectedComponents = new HashSet<AbstractComponent>();
for (View view:selectedManifestations) {
currentSelectedComponents.add(view.getManifestedComponent());
}
boolean sameList = currentSelectedComponents.size() == lastSelectedComponents.size() &&
lastSelectedComponents.containsAll(currentSelectedComponents);
lastSelectedComponents = currentSelectedComponents;
return sameList;
}
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
Collection<View> selectedManifestations = getSelectedManifestations();
if (selectedManifestations.isEmpty())
return;
if (!lastSelectedComponentsAreSame(selectedManifestations) && !isClearingSelection) {
TableViewManifestation.this.firePropertyChange(
SelectionProvider.SELECTION_CHANGED_PROP,
null, selectedManifestations);
}
}
public void clearLastSelectedComponents() {
lastSelectedComponents.clear();
}
}
}