/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.timeseries.ui.manager; import com.bc.ceres.swing.TableLayout; import com.jidesoft.swing.TitledSeparator; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductData; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.ui.UIUtils; import org.esa.snap.core.ui.tool.ToolButtonFactory; import org.esa.snap.netbeans.docwin.WindowUtilities; import org.esa.snap.rcp.actions.view.OpenImageViewAction; import org.esa.snap.rcp.windows.ProductSceneViewTopComponent; import org.esa.snap.timeseries.core.TimeSeriesMapper; import org.esa.snap.timeseries.core.timeseries.datamodel.AbstractTimeSeries; import org.esa.snap.timeseries.core.timeseries.datamodel.AxisMapping; import org.esa.snap.timeseries.core.timeseries.datamodel.ProductLocation; import org.esa.snap.timeseries.core.timeseries.datamodel.ProductLocationType; import org.esa.snap.timeseries.core.timeseries.datamodel.TimeSeriesChangeEvent; import org.esa.snap.timeseries.core.timeseries.datamodel.TimeSeriesListener; import org.esa.snap.timeseries.ui.DefaultProductLocationsPaneModel; import org.esa.snap.timeseries.ui.DefaultVariableSelectionPaneModel; import org.esa.snap.timeseries.ui.ProductLocationsPane; import org.esa.snap.timeseries.ui.ProductLocationsPaneModel; import org.esa.snap.timeseries.ui.Variable; import org.esa.snap.timeseries.ui.VariableSelectionPane; import org.esa.snap.timeseries.ui.VariableSelectionPaneModel; import org.esa.snap.timeseries.ui.assistant.TimeSeriesAssistantAction; import org.esa.snap.ui.NamesAssociationDialog; import org.openide.util.HelpCtx; import org.openide.windows.TopComponent; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.AbstractListModel; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.io.File; import java.net.URL; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; class TimeSeriesManagerForm { private final String helpId; private final DateFormat dateFormat; private final JComponent control; private final FrameClosingTimeSeriesListener frameClosingTimeSeriesListener; private JLabel nameField; private JLabel crsField; private JLabel startField; private JLabel endField; private JLabel dimensionField; private VariableSelectionPane eoVariablePane; private VariableSelectionPane insituVariablePane; private ProductLocationsPane locationsPane; private AbstractButton loadInsituButton; private AbstractButton timeSpanButton; private AbstractButton viewButton; private AbstractTimeSeries currentTimeSeries; private AbstractButton editNamesAssociationButton; TimeSeriesManagerForm(String helpId) { this.helpId = helpId; dateFormat = ProductData.UTC.createDateFormat("dd-MMM-yyyy HH:mm:ss"); control = createControl(); frameClosingTimeSeriesListener = new FrameClosingTimeSeriesListener(); } private JComponent createControl() { final TableLayout layout = new TableLayout(4); layout.setTableAnchor(TableLayout.Anchor.NORTHWEST); layout.setTablePadding(4, 4); layout.setTableFill(TableLayout.Fill.BOTH); layout.setColumnFill(3, TableLayout.Fill.BOTH); layout.setRowWeightY(0, 1.0); layout.setCellWeightY(0, 3, 1.0); layout.setColumnWeightX(0, 1.0); layout.setColumnWeightX(1, 2.0); layout.setColumnWeightX(2, 2.0); layout.setColumnWeightX(3, 0.0); layout.setRowWeightY(1, 2.0); layout.setCellFill(0, 0, TableLayout.Fill.HORIZONTAL); layout.setCellRowspan(0, 3, 2); layout.setCellColspan(1, 0, 3); eoVariablePane = new VariableSelectionPane(); insituVariablePane = new VariableSelectionPane(); JPanel infoPanel = createInfoPanel(); JPanel variablePanel = createVariablePanel("Variables", eoVariablePane); JPanel insituVariablePanel = createVariablePanel("In-situ variables", insituVariablePane); JPanel buttonPanel = createButtonPanel(); JPanel productsPanel = createProductsPanel(); final JPanel control = new JPanel(layout); control.add(infoPanel); control.add(variablePanel); control.add(insituVariablePanel); control.add(buttonPanel); control.add(productsPanel); return control; } public JComponent getControl() { return control; } public void updateFormControl(Product product) { currentTimeSeries = TimeSeriesMapper.getInstance().getTimeSeries(product); if (currentTimeSeries != null) { currentTimeSeries.addTimeSeriesListener(frameClosingTimeSeriesListener); } loadInsituButton.setAction(new LoadInsituAction(currentTimeSeries)); timeSpanButton.setAction(new EditTimeSpanAction(currentTimeSeries)); updateInfoPanel(currentTimeSeries); updateButtonPanel(currentTimeSeries); updateVariablePanel(currentTimeSeries); updateProductsPanel(currentTimeSeries); } private JPanel createInfoPanel() { final TableLayout layout = new TableLayout(2); layout.setTableAnchor(TableLayout.Anchor.NORTHWEST); layout.setTablePadding(4, 4); layout.setColumnWeightX(0, 0.1); layout.setColumnWeightX(1, 1.0); layout.setTableWeightY(0.0); layout.setTableFill(TableLayout.Fill.HORIZONTAL); layout.setCellColspan(0, 0, 2); final JLabel nameLabel = new JLabel("Name:"); nameField = new JLabel(); final JLabel crsLabel = new JLabel("CRS:"); crsField = new JLabel(); final JLabel startLabel = new JLabel("Start time:"); startField = new JLabel(); final JLabel endLabel = new JLabel("End time:"); endField = new JLabel(); final JLabel dimensionLabel = new JLabel("Dimension:"); dimensionField = new JLabel("Dimension:"); final JPanel panel = new JPanel(layout); panel.add(new TitledSeparator("Information")); panel.add(nameLabel); panel.add(nameField); panel.add(crsLabel); panel.add(crsField); panel.add(startLabel); panel.add(startField); panel.add(endLabel); panel.add(endField); panel.add(dimensionLabel); panel.add(dimensionField); return panel; } private void updateInfoPanel(AbstractTimeSeries timeSeries) { if (timeSeries == null) { nameField.setVisible(false); crsField.setVisible(false); startField.setVisible(false); endField.setVisible(false); dimensionField.setVisible(false); return; } final Product tsProduct = timeSeries.getTsProduct(); nameField.setText(tsProduct.getDisplayName()); crsField.setText(tsProduct.getGeoCoding().getMapCRS().getName().getCode()); final String startTime = dateFormat.format(tsProduct.getStartTime().getAsDate()); startField.setText(startTime); String endTime = dateFormat.format(tsProduct.getEndTime().getAsDate()); endField.setText(endTime); final String dimensionString = tsProduct.getSceneRasterWidth() + " x " + tsProduct.getSceneRasterHeight(); dimensionField.setText(dimensionString); } private JPanel createButtonPanel() { final TableLayout layout = new TableLayout(1); layout.setTableAnchor(TableLayout.Anchor.NORTHWEST); layout.setTableFill(TableLayout.Fill.HORIZONTAL); layout.setTableWeightX(1.0); layout.setTableWeightY(0.0); final JPanel panel = new JPanel(layout); panel.add(ToolButtonFactory.createButton(new TimeSeriesAssistantAction(), false)); loadInsituButton = ToolButtonFactory.createButton(new LoadInsituAction(currentTimeSeries), false); panel.add(loadInsituButton); editNamesAssociationButton = ToolButtonFactory.createButton(new EditNameAssociationAction(), false); panel.add(editNamesAssociationButton); timeSpanButton = ToolButtonFactory.createButton(new EditTimeSpanAction(currentTimeSeries), false); panel.add(timeSpanButton); viewButton = ToolButtonFactory.createButton(new ViewTimeSeriesButtonAction(), false); panel.add(viewButton); panel.add(layout.createVerticalSpacer()); AbstractButton helpButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Help22.png"), false); helpButton.addActionListener(e -> new HelpCtx(helpId).display()); helpButton.setToolTipText("Help"); panel.add(helpButton); return panel; } private void showTimeSeriesView(String variableName) { final List<Band> bandList = currentTimeSeries.getBandsForVariable(variableName); for (Band band : bandList) { final TopComponent topComponent = getProductSceneViewTC(band); if (topComponent != null) { return; } } if (!bandList.isEmpty()) { new OpenImageViewAction(bandList.get(0)).openProductSceneView(); } } private TopComponent getProductSceneViewTC(RasterDataNode raster) { return WindowUtilities.getOpened(ProductSceneViewTopComponent.class) .filter(topComponent -> raster == topComponent.getView().getRaster()) .findFirst() .orElse(null); } private void updateButtonPanel(AbstractTimeSeries timeSeries) { boolean enabled = timeSeries != null; viewButton.setEnabled(enabled); loadInsituButton.setEnabled(enabled); editNamesAssociationButton.setEnabled(enabled); } private JPanel createVariablePanel(String title, VariableSelectionPane variablePane) { final TableLayout layout = new TableLayout(1); layout.setTableAnchor(TableLayout.Anchor.NORTHWEST); layout.setTableFill(TableLayout.Fill.HORIZONTAL); layout.setTableWeightX(1.0); layout.setRowWeightY(0, 0.0); layout.setRowWeightY(1, 1.0); layout.setRowFill(1, TableLayout.Fill.BOTH); final JPanel panel = new JPanel(layout); panel.add(new TitledSeparator(title)); variablePane.setPreferredSize(new Dimension(150, 80)); panel.add(variablePane); return panel; } private void updateVariablePanel(AbstractTimeSeries timeSeries) { final VariableSelectionPaneModel model; if (timeSeries != null) { model = new TimeSeriesEoVariableSelectionPaneModel(timeSeries); } else { model = new DefaultVariableSelectionPaneModel(); } eoVariablePane.setModel(model); } private void updateInsituVariablePanel() { final VariableSelectionPaneModel model; if (currentTimeSeries != null) { model = new TimeSeriesInsituVariableSelectionPaneModel(currentTimeSeries); } else { model = new DefaultVariableSelectionPaneModel(); } insituVariablePane.setModel(model); } private JPanel createProductsPanel() { final TableLayout layout = new TableLayout(1); layout.setTableAnchor(TableLayout.Anchor.NORTHWEST); layout.setTableFill(TableLayout.Fill.HORIZONTAL); layout.setTableWeightX(1.0); layout.setRowWeightY(0, 0.0); layout.setRowWeightY(1, 1.0); layout.setRowFill(1, TableLayout.Fill.BOTH); final JPanel panel = new JPanel(layout); panel.add(new TitledSeparator("Product Sources")); locationsPane = new ProductLocationsPane(); locationsPane.setPreferredSize(new Dimension(150, 80)); panel.add(locationsPane); return panel; } private void updateProductsPanel(AbstractTimeSeries timeSeries) { final ProductLocationsPaneModel locationsModel; if (timeSeries != null) { locationsModel = new TimeSeriesProductLocationsPaneModel(timeSeries); } else { locationsModel = new DefaultProductLocationsPaneModel(); } locationsPane.setModel(locationsModel, (timeSeries != null)); } private void closeAssociatedViews(List<Band> bands) { for (Band band : bands) { TopComponent topComponent = getProductSceneViewTC(band); if (topComponent != null) { topComponent.close(); } } } private class TimeSeriesEoVariableSelectionPaneModel extends AbstractListModel implements VariableSelectionPaneModel { private final AbstractTimeSeries timeSeries; private TimeSeriesEoVariableSelectionPaneModel(AbstractTimeSeries timeSeries) { this.timeSeries = timeSeries; } @Override public int getSize() { return timeSeries.getEoVariables().size(); } @Override public Variable getElementAt(int index) { final String varName = timeSeries.getEoVariables().get(index); return new Variable(varName, timeSeries.isEoVariableSelected(varName)); } @Override public void set(Variable... variables) { } @Override public void add(Variable... variables) { } @Override public void setSelectedVariableAt(int index, boolean selected) { final String varName = timeSeries.getEoVariables().get(index); if (timeSeries.isEoVariableSelected(varName) != selected) { if (!selected) { closeAssociatedViews(timeSeries.getBandsForVariable(varName)); } timeSeries.setEoVariableSelected(varName, selected); fireContentsChanged(this, index, index); } } @Override public List<String> getSelectedVariableNames() { final List<String> allVars = timeSeries.getEoVariables(); final List<String> selectedVars = new ArrayList<>(allVars.size()); selectedVars.addAll(allVars.stream().filter(timeSeries::isEoVariableSelected).collect(Collectors.toList())); return selectedVars; } } private static class TimeSeriesInsituVariableSelectionPaneModel extends AbstractListModel implements VariableSelectionPaneModel { private final AbstractTimeSeries timeSeries; private TimeSeriesInsituVariableSelectionPaneModel(AbstractTimeSeries timeSeries) { this.timeSeries = timeSeries; } @Override public int getSize() { return timeSeries.getInsituSource().getParameterNames().length; } @Override public Variable getElementAt(int index) { final String variableName = timeSeries.getInsituSource().getParameterNames()[index]; return new Variable(variableName, timeSeries.isInsituVariableSelected(variableName)); } @Override public void set(Variable... variables) { } @Override public void add(Variable... variables) { } @Override public void setSelectedVariableAt(int index, boolean selected) { String variableName = timeSeries.getInsituSource().getParameterNames()[index]; if (timeSeries.isInsituVariableSelected(variableName) != selected) { timeSeries.setInsituVariableSelected(variableName, selected); fireContentsChanged(this, index, index); } } @Override public List<String> getSelectedVariableNames() { final String[] allVars = timeSeries.getInsituSource().getParameterNames(); final List<String> selectedVars = new ArrayList<>(allVars.length); for (String varName : allVars) { if (timeSeries.isInsituVariableSelected(varName)) { selectedVars.add(varName); } } return selectedVars; } } private class TimeSeriesProductLocationsPaneModel extends AbstractListModel implements ProductLocationsPaneModel { private final AbstractTimeSeries timeSeries; private TimeSeriesProductLocationsPaneModel(AbstractTimeSeries timeSeries) { this.timeSeries = timeSeries; } @Override public int getSize() { return timeSeries.getProductLocations().size(); } @Override public ProductLocation getElementAt(int index) { return timeSeries.getProductLocations().get(index); } @Override public List<ProductLocation> getProductLocations() { return timeSeries.getProductLocations(); } @Override public void addFiles(File... files) { final int startIndex = timeSeries.getProductLocations().size(); for (File file : files) { timeSeries.addProductLocation(new ProductLocation(ProductLocationType.FILE, file.getAbsolutePath())); } final int stopIndex = timeSeries.getProductLocations().size() - 1; fireIntervalAdded(this, startIndex, stopIndex); } @Override public void addDirectory(File directory, boolean recursive) { timeSeries.addProductLocation( new ProductLocation(recursive ? ProductLocationType.DIRECTORY_REC : ProductLocationType.DIRECTORY, directory.getAbsolutePath())); final int index = timeSeries.getProductLocations().size() - 1; fireIntervalAdded(this, index, index); } @Override public void remove(int... indices) { final List<ProductLocation> locationList = timeSeries.getProductLocations(); final List<ProductLocation> toRemove = new ArrayList<>(); for (int index : indices) { toRemove.add(locationList.get(index)); } for (ProductLocation location : toRemove) { closeAssociatedViews(timeSeries.getBandsForProductLocation(location)); timeSeries.removeProductLocation(location); } if (!toRemove.isEmpty()) { fireContentsChanged(this, indices[0], indices[indices.length - 1]); } } } private class EditNameAssociationAction extends AbstractAction { private EditNameAssociationAction() { URL viewIconImageURL = UIUtils.getImageURL("/org/esa/snap/timeseries/ui/icons/timeseries-combvar24.png", TimeSeriesManagerForm.class); putValue(LARGE_ICON_KEY, new ImageIcon(viewIconImageURL)); putValue(SHORT_DESCRIPTION, "Edit names association"); } @Override public void actionPerformed(ActionEvent e) { final NamesAssociationDialog.AssociationModel associationModel = getAssociationModel(); final NamesAssociationDialog.NameProvider nameProvider = getNameProvider(); final String helpId = "associations"; NamesAssociationDialog.show(associationModel, nameProvider, helpId); } } private class ViewTimeSeriesButtonAction extends AbstractAction { private ViewTimeSeriesButtonAction() { URL viewIconImageURL = UIUtils.getImageURL("/org/esa/snap/timeseries/ui/icons/timeseries-view24.png", TimeSeriesManagerForm.class); putValue(LARGE_ICON_KEY, new ImageIcon(viewIconImageURL)); putValue(SHORT_DESCRIPTION, "View Time Series"); } @Override public void actionPerformed(ActionEvent e) { final VariableSelectionPaneModel variableModel = eoVariablePane.getModel(); final List<String> variableNames = variableModel.getSelectedVariableNames(); if (!variableNames.isEmpty() && currentTimeSeries != null) { if (variableNames.size() == 1) { showTimeSeriesView(variableNames.get(0)); } else { JPopupMenu viewPopup = new JPopupMenu("View variable"); for (String varName : variableNames) { viewPopup.add(new ViewTimeSeriesAction(varName)); } final Rectangle buttonBounds = viewButton.getBounds(); viewPopup.show(viewButton, 1, buttonBounds.height + 1); } } } } private class ViewTimeSeriesAction extends AbstractAction { private final String variableName; private ViewTimeSeriesAction(String variableName) { super("View " + variableName); this.variableName = variableName; } @Override public void actionPerformed(ActionEvent e) { showTimeSeriesView(variableName); } } private NamesAssociationDialog.AssociationModel getAssociationModel() { final AxisMapping axisMapping = currentTimeSeries.getAxisMapping(); return new NamesAssociationDialog.AssociationModel() { @Override public List<String> getRightListNames(String alias) { return axisMapping.getInsituNames(alias); } @Override public List<String> getCenterListNames(String alias) { return axisMapping.getRasterNames(alias); } @Override public void addFromCenterList(String alias, String name) { axisMapping.addRasterName(alias, name); } @Override public void addFromRightList(String alias, String name) { axisMapping.addInsituName(alias, name); } @Override public void removeAlias(String alias) { axisMapping.removeAlias(alias); } @Override public void addAlias(String alias) { axisMapping.addAlias(alias); } @Override public void removeFromRightList(String alias, String name) { axisMapping.removeInsituName(alias, name); } @Override public void removeFromCenterList(String alias, String name) { axisMapping.removeRasterName(alias, name); } @Override public Set<String> getAliasNames() { return axisMapping.getAliasNames(); } @Override public void replaceAlias(String beforeName, String changedName) { axisMapping.replaceAlias(beforeName, changedName); } }; } private NamesAssociationDialog.NameProvider getNameProvider() { final String windowTitle = "Names Association"; final String aliasHeaderName = "Association name:"; final String centerHeaderName = "Names from time series:"; final String rightHeaderName = "Names from insitu file:"; return new NamesAssociationDialog.NameProvider(windowTitle, aliasHeaderName, centerHeaderName, rightHeaderName) { @Override public String[] getCenterNames() { final List<String> eoVariables = currentTimeSeries.getEoVariables(); return eoVariables.toArray(new String[eoVariables.size()]); } @Override public String[] getRightNames() { if (currentTimeSeries.getInsituSource() != null) { return currentTimeSeries.getInsituSource().getParameterNames(); } else { return new String[0]; } } }; } private class FrameClosingTimeSeriesListener extends TimeSeriesListener { @Override public void timeSeriesChanged(TimeSeriesChangeEvent event) { if (event.getType() == TimeSeriesChangeEvent.BAND_TO_BE_REMOVED) { Band band = (Band) event.getValue(); closeAssociatedViews(Collections.singletonList(band)); } else if (event.getType() == TimeSeriesChangeEvent.INSITU_SOURCE_CHANGED) { updateInsituVariablePanel(); } } } }